面试题1:说一下 HashMap 的实现原理?
- 追问1:如何实现HashMap的有序?
- 追问2:那TreeMap怎么实现有序的?
- 追问3:put方法原理是怎么实现的?
- 追问4:HashMap扩容机制原理
- 追问5:HashMap在JDK1.8都做了哪些优化?
- 追问6:链表红黑树如何互相转换?阈值多少?
面试题2:HashMap是线程安全的吗?
- 追问1:你是如何解决这个线程不安全问题的?
- 追问2:说一下大家为什么要选择ConcurrentHashMap?
- 追问3:ConcurrentHashMap在JDK1.7、1.8中都有哪些优化?
面试题1:说一下 HashMap 的实现原理?
正经回答:
众所周知,HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry(包括Key-Value),其中Key 和 Value 允许为null。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干。另外,HashMap数组每一个元素的初始值都是Null。
值得注意的是:HashMap不能保证映射的顺序,插入后的数据顺序也不能保证一直不变(如扩容后rehash)。
要说HashMap的原理,首先要先了解它的数据结构
如上图为JDK1.8版本的数据结构,其实HashMap在JDK1.7及以前是一个“链表散列”的数据结构,即数组 + 链表的结合体。JDK8优化为:数组+链表+红黑树。
我们常把数组中的每一个节点称为一个桶。当向桶中添加一个键值对时,首先计算键值对中key的hash值(hash(key)),以此确定插入数组中的位置(即哪个桶),但是可能存在同一hash值的元素已经被放在数组同一位置了,这种现象称为碰撞,这时按照尾插法(jdk1.7及以前为头插法)的方式添加key-value到同一hash值的元素的最后面,链表就这样形成了。
当链表长度超过8(TREEIFY_THRESHOLD - 阈值)时,链表就自行转为红黑树。
注意:同一hash值的元素指的是key内容一样么?不是。根据hash算法的计算方式,是将key值转为一个32位的int值(近似取值),key值不同但key值相近的很可能hash值相同,如key=“a”和key=“aa”等
通过上述回答的内容,我们明显给了面试官往深入问的多个诱饵,根据我们的回答,下一步他多可能会追问这些问题:
1、如何实现HashMap的有序?
4、put方法原理是怎么实现的?
6、扩容机制原理 → 初始容量、加载因子 → 扩容后的rehash(元素迁移)
2、插入后的数据顺序会变的原因是什么?
3、HashMap在JDK1.7-JDK1.8都做了哪些优化?
5、链表红黑树如何互相转换?阈值多少?
7、头插法改成尾插法为了解决什么问题?
而我们,当然是提前准备好如何回答好这些问题!当你的回答超过面试同学的认知范围时,主动权就到我们手里了。
追问1:如何实现HashMap的有序?
使用LinkedHashMap 或 TreeMap。
LinkedHashMap内部维护了一个单链表,有头尾节点,同时LinkedHashMap节点Entry内部除了继承HashMap的Node属性,还有before 和 after用于标识前置节点和后置节点。可以实现按插入的顺序或访问顺序排序。
/**
* The head (eldest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* The tail (youngest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> tail;
//将加入的p节点添加到链表末尾
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
//LinkedHashMap的节点类
static class Entry<K,V> extends HashMap.Node<