目录
一、Java 基础
1.JDK 和 JRE 有什么区别?
- JRE:Java Runtime Environment,是Java运行环境,如果你不需要开发只需要运行Java程序,那么你可以安装JRE。例如程序员开发出的程序最终卖给了用户,用户不用开发,只需要运行程序,所以用户在电脑上安装JRE即可。
- JDK:Java Development Kit,它是Java开发运行环境,在程序员的电脑上当然要安装JDK;JDK包含了JRE。
2.== 和 equals 的区别是什么?
- ==:比较的对象引用,也就是内存地址是否相等
- equals:比较的是值
2.Java重写equals()
- 保证具有对称性、传递性、一致性(自反性和非空性自动满足)
public class ColorPoint{
private Point point;
private Color color;
public ColorPoint(int x, int y, Color color){
point = new Point(x, y);
this.color = color;
}
//返回一个与该有色点在同一位置上的普通Point对象
public Point asPoint(){
return point;
}
public boolean equals(Object o){
if(o == this)
return true;
if(!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return cp.point.equals(point)&&
cp.color.equals(color);
}
}
3.hashCode()方法的作用是什么?
- Java Set中的元素是不可重复的,可两个元素是否重复应该依据什么来判断呢?这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。 于是,Java采用了哈希表的原理。哈希算法将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。 这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
3.两个对象的 hashCode()相同,则 equals()也一定为 tr ue,对吗?
- 不对,存在hash冲突
4.final 在 java 中有什么作用?
- 终态,修饰类不能被继承、修饰函数不能被重写、修饰变量则变量只能读不能写;
5.java 中的 Math.round(-1.5) 等于多少?
- Math.round()是四舍五入函数,返回值为int型
6.String 属于基础的数据类型吗?
- 不是,是封装类型
7.java 中操作字符串都有哪些类?它们之间有什么区别?
- String:不可变对象,不能修改
- StringBuffer:长度可变,线程安全
- StringBulider:长度可变,线程不安全
8.String str1="i"、String str2="i"与 String str3=new String(“i”)一样吗?
- str1与str2一样,str1与str3不一样。
- String str="i"; 因为String 是final类型的,所以“i”应该是在常量池。
- new String("i");则是新建对象放到堆内存中。
9.如何将字符串反转?
- StringBuilder(str).reverse().toString();
10.String 类的常用方法都有那些?
- length()、toCharArray()、split(String)、contains(String)、startsWith(String)、endsWith(String)、toUpperCase()、replace(String,String)、substring(int,int)、trim()、charAt(int)、indexOf(String)
11.抽象类必须要有抽象方法吗?
- 抽象类不一定有抽象方法;但是包含一个抽象方法的类一定是抽象类。(有抽象方法就是抽象类)
- 在abstract class 中可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface中,只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在 interface中一般不定义数据成员),所有的成员方法都是abstract的。
12.普通类和抽象类有哪些区别?
- 抽象类不能被实例化
13.抽象类能使用 final 修饰吗?
- 抽象方法不能用final修饰,因为子类需要重写抽象方法,如果被final修饰则无法重写
14.接口和抽象类有什么区别?
相同点:
- 都不能实例化对象
- 都可以包含抽象方法,而且抽象方法必须被继承的类全部实现。
不同点:
- 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
- 接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
- 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量
- 抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果
- 抽象类里可以没有抽象方法
- 抽象方法要被实现,所以不能是静态的,也不能是私有的。
- 接口可继承接口,并可多继承接口,但类只能单根继承。
15.java 中 IO 流分为几种?
- BIO:同步阻塞I/O模式
- NIO:同步非阻塞的I/O模型
- AIO:异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
二、容器
18.java 容器都有哪些?
- ArrayList、LinkedList、Set、TreeSet、HashMap、TreeMap、Deque、PriorityQueue
19.Collection 和 Collections 有什么区别?
- java.util.Collection 是一个集合接口;
- java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等),此类不能实例化,就像一个工具类,服务于Java的Collection框架。
20.List、Set、Map 之间的区别是什么?
- List:有序集合
- Set:不重复集合,LinkedHashSet按照插入排序,SortedSet可排序,HashSet无序
- Map:键值对集合
21.HashMap 和 Hashtable 有什么区别?
- Hashtable:底层数组+链表实现,无论key还是value都不能为null,线程安全
- HashMap :底层数组+链表或数组加红黑树实现,可以存储null键和null值,线程不安全
22.如何决定使用 HashMap 还是 TreeMap?
- 不需要排序使用HashMap,需要排序使用TreeMap
23.说一下 HashMap 的实现原理?
- 用于存储Key-value集合的数据结构;底层用数组加链表或数组加红黑树实现,当链表长度超过8时转为红黑树;初识容量是16,扩容是2倍容量扩容;当数组大小使用率75%时触发扩容
24.说一下 HashSet 的实现原理?
- HashSet是一个HashMap实例,都是一个存放链表的数组。HashSet中不允许有重复元素,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个固定对象private static final Object PRESENT = new Object();
- HashSet中add方法调用的是底层HashMap中的put()方法,而如果是在HashMap中调用put,首先会判断key是否存在,如果key存在则修改value值,如果key不存在这插入这个key-value。而在set中,因为value值没有用,也就不存在修改value值的说法,因此往HashSet中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样HashSet中就不存在重复值。
- 所以判断key是否存在就要重写元素的类的equals()和hashCode()方法,当向Set中添加对象时,首先调用此对象所在类的hashCode()方法,计算次对象的哈希值,此哈希值决定了此对象在Set中存放的位置;若此位置没有被存储对象则直接存储,若已有对象则通过对象所在类的equals()比较两个对象是否相同,相同则不能被添加。
25.ArrayList 和 LinkedList 的区别是什么?
- ArrayList:底层是数组,查找快,增加删除慢
- LinkedList:底层是链表,查找慢,增加删除快
26.如何实现数组和 List 之间的转换?
- 数组转 List ,使用 JDK 中 java.util.Arrays 工具类的 asList 方法
- List 转数组,使用 List 的toArray方法
27.ArrayList 和 Vector 的区别是什么?
- Vector:线程安全的动态数组,虽然是线程安全的,但性能较差,一般情况下使用ArrayList,除非特殊需求;
- ArrayList:线程不安全
28.Array 和 ArrayList 有何区别?
- Array:可以包含基本类型和对象类型,大小是固定的
- ArrayList:只能包含对象类型,大小是动态变化的。
- ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
29.在 Queue 中 poll()和 remove()有什么区别?
- 异常处理方式不一样,offer()/peek()/poll()异常时返回null或false;add()/element()/remove()抛出异常30.哪些集合类是线程安全的?
31.迭代器 Iterator 是什么?
- 用于顺序访问集合对象的元素,无需知道集合对象的底层实现。
32.Iterator 怎么使用?有什么特点?
- 使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
- 使用next()获得序列中的下一个元素
- 使用hasNext()检查序列中是否还有元素。
- 使用remove()将迭代器新近返回的元素删除。
- 使用迭代器的remove()方法
- 使用for正循环遍历,每次删除时修改下标i=i-1
public static void main(String[] args) {
List<String> platformList = new ArrayList<>();
platformList.add("博客园");
platformList.add("优快云");
platformList.add("掘金");
Iterator<String> iterator = platformList.iterator();
while (iterator.hasNext()) {
String platform = iterator.next();
if (platform.equals("博客园")) {
iterator.remove();
}
}
System.out.println(platformList);
}
33.Iterator 和 ListIterator 有什么区别?
- ListIterator 继承 Iterator
- ListIterator 比 Iterator多方法
- 使用范围不同,Iterator可以迭代所有集合;ListIterator 只能用于List及其子类
34.怎么确保一个集合不能被修改?
- 首先我们要清楚,集合(map,set,list…)都是引用类型,所以我们如果用final修饰的话,集合里面的内容还是可以修改的。可以采用Collections包下的unmodifiableMap方法,通过这个方法返回的map,是不可以修改的
35.unmodifiableMap不能修改的原理:
- 当调用其读相关方法(size、isEmpty、get)时,间接调用的是成员属性m的对应方法。
- 当调用其修改相关方法(put、remove、clear)时,直接抛出
UnsupportedOperationException
异常,不让修改。
三、多线程
35.并行和并发有什么区别?
- 并行:进程并行执行,多核cpu并行执行多个进程
- 并发:一个进程的多个线程并发,并发只是看起来像同时执行,其实是轮流使用cpu的
36.线程和进程的区别?
1)功能不同
- 进程就是运行中的程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
- 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
2)工作原理不同
- 进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
- 线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。
3)作用不同
- 进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
- 通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。
36.python协程是什么?与线程有什么区别?
- 进程上的一把锁,python程序运行时,只有一个线程可以获取这把锁,这也是python为什么是伪并发的原因
37.守护线程是什么?
- java里线程分2种,守护线程、用户线程。守护线程是专门用于服务其他的线程,比如垃圾回收线程,就是最典型的守护线程。
- 如果其他的线程(即用户自定义线程)都执行完毕,连main线程也执行完毕,那么jvm就会退出(即停止运行)——此时,连jvm都停止运行了,守护线程当然也就停止执行了。
- 当线程只剩下守护线程的时候,JVM就会退出;补充一点如果还有其他的任意一个用户线程还在,JVM就不会退出。
37.使用守护线程需要注意什么?
- thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
- 在Daemon线程中产生的新线程也是Daemon的。
- 守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。
- Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线程就不能用Java的线程池。
38.创建线程有哪几种方式?
- 两种,继承Thread类或者实现Runnable接口,因为Java不允许多继承,但允许实现多个接口,所以推荐使用实现Runnable接口的方式
39.说一下 runnable 和 callable 有什么区别?
- 两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
- Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
39.callable创建线程的demo
public class CallableImpl implements Callable<String> {
private String acceptStr;
public CallableImpl(String acceptStr) {
this.acceptStr = acceptStr;
}
@Override
public String call() throws Exception {
// 任务阻塞 1 秒
Thread.sleep(1000);
return this.acceptStr + " append some chars and return it!";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> callable = new CallableImpl("my callable test!");
FutureTask<String> task = new FutureTask<>(callable);
long beginTime = System.currentTimeMillis();
// 创建线程
new Thread(task).start();
// 调用get()阻塞主线程,反之,main线程不会阻塞
String result = task.get();
long endTime = System.currentTimeMillis();
System.out.println("hello : " + result);
System.out.println("cast : " + (endTime - beginTime) / 1000 + " second!");
}
}
40.线程有哪些状态?
- 创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)
41.sleep() 和 wait() 有什么区别?
- wait、notify/notifyAll是Object的本地final方法,wait使当前线程阻塞,前提是必须先获得锁,所以只能在synchronized锁范围内里使用wait、notify/notifyAll方法,
- sleep方法是Thead类的静态方法,sleep可以在任何地方使用,
- sleep() 和 wait() 都会释放cpu资源,但sleep()不会释放锁,wait()会释放锁
42.notify()和 notifyAll()有什么区别?
- notify():唤醒等待线程
- notifyAll():唤醒所有等待线程,但最终也只有一个线程能够获得锁
42.选择使用notify还是notifyAll()?
- notify方法和notifyAll()方法两者非常相似,到底该用哪一个,老实说,这个选择有点困难。选择notify的话,因为要唤醒的线程比较少(only one),程序的处理速度当然比notifyAll略胜一筹。但是选择notify时,若这部分处理不好,可能会出现程序挂掉的危险。一般说来,选择notifyAll所写出来的程序代码会比notify可靠。除非你能确定程序员对程序代码的意义和能力限度一清二楚,否则选择notifyAll应该是比较稳扎稳打。
43.线程的 run()和 start()有什么区别?
- start() 方法是用来启动线程的,轮到该线程执行时,会自动调用 run();
- 直接调用 run() 方法,无法达到启动多线程的目的,相当于主线程线性执行 Thread 对象的 run() 方法。
- 一个线程对线的 start() 方法只能调用一次,多次调用会抛出 java.lang.IllegalThreadStateException 异常;run() 方法没有限制。
44.创建线程池有哪几种方式?
- 通过Executors工厂方法创建
- 通过new
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)自定义创建
推荐:使用ThreadPoolExecutor方式创建线程池(阿里不允许使用
Executors,使用ThreadPoolExecutor可以避免资源耗尽的风险)
//创建使用单个线程的线程池
ExecutorService es1 = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
es1.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行任务");
}
});
}
//创建使用固定线程数的线程池
ExecutorService es2 = Executors.newFixedThreadPool(3);
//创建一个会根据需要创建新线程的线程池
ExecutorService es3 = Executors.newCachedThreadPool();
//创建拥有固定线程数量的定时线程任务的线程池
ScheduledExecutorService es4 = Executors.newScheduledThreadPool(2);
//创建只有一个线程的定时线程任务的线程池
ScheduledExecutorService es5 = Executors.newSingleThreadScheduledExecutor();
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 20, 2, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));
for (int i = 0; i < size; i++) {
poolExecutor.execute(new DemoTask(i));
Console.log("poolSize:" + poolExecutor.getPoolSize());
}
46.线程池中 submit()和 execute()方法有什么区别?
- 参数不同:execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)
- 返回值不同:execute() 没有返回值;而 submit() 有返回值,submit() 的返回值 Future 调用get方法时,可以捕获处理异常
- 总结:推荐使用submit()
45.线程池都有哪些状态?
- RUNNING
- SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。调用shutdown()方法时变为这个状态
- STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。调用shutdownNow()方法时变为