0x00 equalshashCode和==的关系

==对于基本数据类型比较的是值,对于对象,比较的是对象的堆地址是否相等。Object类中equals()方法底层依赖的是==,默认的Object类中使用equals方法也是对比的对象的堆地址是否相等。hashCode是计算的对象的散列值。三者关系如下:

  1. 两个对象的hashcode相同,对象不一定是同一个对象。
  2. 两个对象的hashcode不同,那一定不是同一个对象。
  3. 如果两个对象的equals相同,那么hashcode一定相同。

有关String对象的特殊说明,看下面的代码:

1
2
3
4
5
6
String a = "ab";
String b = "ab";
System.out.println(a == b);
String c = "a";
String d = c + "b";
System.out.println(a == d);

第一个输出为true,因为"ab"为字符串直接量,同样的字符串直接量将被存储为一个实例,所以a和b都指向一个实例,地址相同,==结果即为true。而a和d的地址不同,所以比较结果为false。而再看这段代码:

1
2
3
4
5
6
String a = "ab";
String b = "ab";
System.out.println(a.hashCode() == b.hashCode());
String c = "a";
String d = c + "b";
System.out.println(a.hashCode() == d.hashCode());

首先明确,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>。简而言之就是:

  1. 如果你需要从集合中获取类型T,那就使用<? extends T>
  2. 如果你需要将类型T放入集合中,那就使用<? super T>
  3. 如果你既要获取又要放置元素,那就不使用任何通配符。

例如在java.util.Collections中的集合复制的方法:

1
2
3
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
...
}

复制源集合src,主要获得元素,所以用<? extends T>。复制目标集合dest,主要是设置元素,所以用<? super T>

0x02 抽象类和接口的区别

  1. 接口的方法默认是public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法;
  2. 接口中的实例变量默认是final类型的,而抽象类中则不一定。
  3. 一个类可以实现多个接口,但最多只能继承一个抽象类。
  4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定。
  5. 在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 RunnableCallable的区别

  1. Callable规定的方法是call()Runnable规定的方法是run()
  2. Callable的任务执行后可返回值,而Runnable的任务是不能返回值(返回值类型为void);
  3. call()方法可以抛出异常,run()方法不可以;
  4. 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果;
  5. 加入线程池运行,Runnable使用ExecutorServiceexecute方法,Callable使用submit方法。

0x05 TreeMapHashMapLinkedHashMap

  1. HashMap中的元素是没有顺序的,而TreeMap中所有的元素都是有某一固定顺序的,如果想要得到某一有序的遍历结果应该使用TreeMap
  2. TreeMapHashMap都不是线程安全的;
  3. HashMap基于hash表实现的,而TreeMap是基于红黑树实现的;
  4. 为了优化HashMap的空间使用,可以调优初始容量和负载因子,而TreeMap就没有调优选项,因为红黑树总是处于平衡的状态;
  5. HashMap适用于Map的插入、删除和定位元素,而TreeMap适用于按自然顺序或自定义顺序遍历键(key)。

HashMap是无序的,当我们希望有顺序地去存储key-value时,就需要使用LinkedHashMap了。其默认采用写入顺序进行排序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。也可以在构造时使用参数指定根据访问顺序排序

0x06 Synchronized和Lock的区别

  1. synchronized是一个java关键词