文章目录
1.ArrayList、Vector、LinkedList的关系与区别?
关系: ArrayList、Vector、LinkedList都是List接口的子类、用于保存单个元素。
区别:
- ArrayList和Vector底层基于数组,但ArrayList是JDK1.2产生的,初始化时属于懒加载策略,在第一次添加元素的时候初始化,初始化数组大小为10,扩容时扩容为原来的1.5倍,异步处理线程不安全。Vector是JDK1.0产生的,初始化数组大小为10。扩容时扩容为原来的2倍,同步处理线程安全。
- LinkedList底层基于双向链表,是JDK1.2产生的,因为是链表也就不存在扩容的情况,异步处理,线程不安全。
综上所述:
- 当需要一个线程安全的集合时选择Vector
- 当操作大部分为添加或者删除的时候选择LinkedList
- 当操作大部分是查找的时候选择ArrayList
2.什么是fail-fast机制?什么是fail-safe?
fail-fast也叫做快速失败
其中有一个modcount变量记录当前集合被修改的次数,当集合添加元素或者删除元素的时候modcount都会加1,在集合遍历时迭代器每次都会判断modcount的值是否与集合遍历前的modcount值相等,如果相等继续遍历,如果不相等就抛出并发修改异常。这是为了避免在遍历数据的时候仍对集合进行增加或者删除,产生脏读。
ArrayList、Vector、LinkedList都是fail-fast机制
fail-safe也叫做安全失败
安全失败和快速失败相反即使modcount的值与集合遍历前dcount的值不相等,也会继续遍历不会抛出异常。如:ConcurrentHashMap
3.Set接口与Map接口的关系?
Set的本质就是使用Map来存储元素、保存到Map的key上、其中value为Object的对象。
public HashSet() {
map = new HashMap<>();
}
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
4.hashcode与equals的关系?
hashcode() 将任意一个对象根据指定的算法转为32位的整数
equals() 判断两个对象是否相等
hashcode()相同的两个对象equals()不一定相等
equals()相同的两个对象hashcode()一定相等
5.Java中一个类的两个对象比较大小的方式?
内部排序:
实现Comparable接口覆写compareTo 方法
外部排序:
构造方法中传入Comparator对象,覆写compare方法
当内部排序与外部排序共存时,优先使用的是外部排序
6.HashMap、TreeMap、Hashtable的关系与区别?
关系: HashMap、TreeMap、Hashtable都是Map的子类、存放键值对
区别:
- HashMap底层基于哈希表和红黑树、TreeMap底层 红黑树、Hashtable底层基于哈希表
- HashMap允许key和value均为null TreeMap中key不能为空,value可以为空 Hashtable中key和value均不可能为空
- HashMap和TreeMap属于异步处理、线程不安全,效率高
- Hashtable使用Synchronized同步方法,将整个哈希表上锁,线程安全,但是锁粒度太粗导致性能很低
7. HashMap的源码解读?
初始化:
HashMap初始化时如果没有传入容量,默认会在第一次put的时候初始化哈希表的长度为16,如果传入了容量,会将传入的容量换为最近的2m的值,在第一次put的时候初始化哈希表大小。
添加元素:
在添加元素的时候首先会判断哈希表有没有初始化,如果没有初始化,则初始化,如果初始化了,则根据key值计算的hash确定桶的下标,如果桶的头结点没有元素,则将该元素添加到头结点处,如果有元素,且桶下的元素已经树化,则根据树的方式插入,如果桶下面的元素没有树化,则尾插至链表尾部,如果在遍历的过程中遇到key值相同的元素则替代,添加完成之后,会判断桶下的元素个数有没有等于8,如果等于8,且哈希表的元素超过了64则进行树化,将该桶下的结点转为红黑树存放,提高效率。如果整个哈希表的元素没有超过64则进行扩容,扩容时哈希表扩容为原来的两倍,扩容之后元素需要重新挪动位置,如果桶下面的元素已经树化,在挪动元素的过程中,发现元素个数小于6则又会还原为链表。
哈希函数:
h = key == null ? 0:(h = key.hasCode() ) ^ h>>>16;
为什么HashMap不直接采用key值的hashcode方法计算哈希值?
hashcode()方法返回的是一个32位的整数,直接将其作为下标会浪费大量的空间
h >>> 16只保留16位,将高低16位都参与异或运算,减少哈希冲突。
真正的数组下标:(n-1) & hash 是为了保证计算的值一定在数组长度的范围内
为什么要树化?
当链表的长度过长时,哈希表的增删改查时间负载度退化为0(n),将链表转为红黑树,时间复杂度为0(nlogn)
8.JDK1.7和JDK1.8中ConcurrentHashMap的设计区别?
JDK1.7
JDK1.7的ConcurrentHashMap采用的是Segment+哈希表,并且Segment初始化为16之后无法扩容,只能对每个Segment下的哈希表进行扩容,之前Hashtable是将整个表上锁,JDK1.7的ConcurrentHashMap使用Lock体系保证线程安全,将锁细粒度化为每个Segment上加锁。(Segment是ReentrantLock的子类)
JDK1.8
JDK1.8采用的是哈希表+红黑树,不在使用Segment,使用synchronized保证线程安全,锁粒度再次细化为哈希表的桶数组元素。(扩容后锁增多)