Java 基础汇总
0x00 equals
、hashCode
和==的关系
==对于基本数据类型比较的是值,对于对象,比较的是对象的堆地址是否相等。Object类中equals()方法底层依赖的是==,默认的Object类中使用equals方法也是对比的对象的堆地址是否相等。hashCode是计算的对象的散列值。三者关系如下:
- 两个对象的hashcode相同,对象不一定是同一个对象。
- 两个对象的hashcode不同,那一定不是同一个对象。
- 如果两个对象的equals相同,那么hashcode一定相同。
有关String对象的特殊说明,看下面的代码:
1 | String a = "ab"; |
第一个输出为true,因为"ab"为字符串直接量,同样的字符串直接量将被存储为一个实例,所以a和b都指向一个实例,地址相同,==结果即为true。而a和d的地址不同,所以比较结果为false。而再看这段代码:
1 | String a = "ab"; |
首先明确,String类的hashCode值是和其保存的字符串有直接关系的,相同的字符串将会有相同的hashCode值,所以上述代码中,两个地址均输出true。String类型的hashCode计算规则为:
1 | s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] |
0x01 泛型中的<? super T>
和<? extends T>
首先,<? super T>
表示包括T在内的任何T的父类,<? extends T>
表示包括T在内的任何T的子类。你不能往List<? extends T>
中插入任何类型的对象,因为你不能保证列表实际指向的类型是什么,你并不能保证列表中实际存储什么类型的对象。唯一可以保证的是,你可以从中读取到T或者T的子类。相反,如果是super
就可以写入,因为其基本原则就是,可以将子类的对象赋值给父类,而不可以将父类的对象赋值给子类。同时,List<? super T>
往外取时只能放在Object对象里。
与之相关的则是PECS原则,即Producer Extends Consumer Super,换句话说,生产者(外界频繁读取数据的)使用<? extends T>
,消费者使用<? super T>
。简而言之就是:
- 如果你需要从集合中获取类型T,那就使用
<? extends T>
; - 如果你需要将类型T放入集合中,那就使用
<? super T>
; - 如果你既要获取又要放置元素,那就不使用任何通配符。
例如在java.util.Collections
中的集合复制的方法:
1 | public static <T> void copy(List<? super T> dest, List<? extends T> src) { |
复制源集合src
,主要获得元素,所以用<? extends T>
。复制目标集合dest
,主要是设置元素,所以用<? super T>
。
0x02 抽象类和接口的区别
- 接口的方法默认是public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法;
- 接口中的实例变量默认是
final
类型的,而抽象类中则不一定。 - 一个类可以实现多个接口,但最多只能继承一个抽象类。
- 一个类实现接口的话要实现接口的所有方法,而抽象类不一定。
- 在JDK8中,接口也可以定义静态方法,可以直接用接口名调用。
0x03 IO
0x00 BIO(同步阻塞IO)
线程发起IO请求,不管内核是否做好IO准备,从发起请求起,线程一直阻塞,直到操作完成。其根本特性就是做完一件事再去做另一件事,一件事做之前一定要等到前一件事做完。如果线程在执行过程中依赖于需要等待的资源,那么该线程会长期处于阻塞状态,此时处理机就会进行线程的切换,如果在高并发的web或者tcp服务器中系统开辟成千上万的线程,那么处理机的时间就会浪费在线程的切换中,使得线程的执行效率大大降低。
0x01 NIO(同步非阻塞IO)
线程发起IO请求,立即返回,内核在做好IO操作准备后,通过调用注册的回调函数通知线程做IO操作,线程开始阻塞,直到操作完成。客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。NIO适用于连接数目较多且连接比较短的架构,比如聊天服务器。
NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。
0x02 AIO(异步非阻塞IO)
异步非阻塞I/O,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由操作系统先完成了再通知服务器用其启动线程进行处理。对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序。对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。AIO相对于NIO的区别在于,NIO需要使用者线程不停的轮询IO对象,来确定是否有数据准备好可以读了,而AIO则是在数据准备好之后,才会通知数据使用者,这样使用者就不需要不停地轮询了。
0x03 同步与异步的区别
同步就是发送一个请求,等待返回,再发送下一个请求,同步可以避免出现死锁,脏读的发生。异步就是发送一个请求,不等待返回,随时可以再发送下一个请求,可以提高效率,保证并发。
0x04 阻塞与非阻塞的区别
阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
0x04 Runnable
和Callable
的区别
Callable
规定的方法是call()
,Runnable
规定的方法是run()
;Callable
的任务执行后可返回值,而Runnable
的任务是不能返回值(返回值类型为void
);call()
方法可以抛出异常,run()
方法不可以;- 运行
Callable
任务可以拿到一个Future
对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future
对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果; - 加入线程池运行,
Runnable
使用ExecutorService
的execute
方法,Callable
使用submit
方法。
0x05 TreeMap
、HashMap
和LinkedHashMap
HashMap
中的元素是没有顺序的,而TreeMap
中所有的元素都是有某一固定顺序的,如果想要得到某一有序的遍历结果应该使用TreeMap
;TreeMap
和HashMap
都不是线程安全的;HashMap
基于hash表实现的,而TreeMap
是基于红黑树实现的;- 为了优化
HashMap
的空间使用,可以调优初始容量和负载因子,而TreeMap
就没有调优选项,因为红黑树总是处于平衡的状态; HashMap
适用于Map
的插入、删除和定位元素,而TreeMap
适用于按自然顺序或自定义顺序遍历键(key)。
HashMap
是无序的,当我们希望有顺序地去存储key-value时,就需要使用LinkedHashMap
了。其默认采用写入顺序进行排序,在用Iterator
遍历LinkedHashMap
时,先得到的记录肯定是先插入的。也可以在构造时使用参数指定根据访问顺序排序。
0x06 Synchronized和Lock的区别
synchronized
是一个java关键词