1.说一下HashMap的实现原理?
难易程度:3
出现程度:5
1)底层使用hash表数据结构,即数组+(链表|红黑树)
2)添加数据时,计算key的值确定元素在数组中的下标
a.key相同则替换
b.不同则存入链表或红黑树中
获取数据通过key的hash计算数组下标获取元素
2.hashmap的jdk1.7和jdk1.8有什么区别?
jdk1.8之前采用的拉链法,数组+链表
jdk1.8之后采用数组+链表+红黑树,链表长度大于8且数组长度大于64则会从链表转化为红黑树
3.hashmap的put方法的具体流程
难易程度:4
出现频率:5
1)判断键值对数组table是否为空或者为null,否则执行resize()进行扩容(初始化)
2)根据键值key计算hash值得到数组索引
3)判断table[i]==null,条件成立,直接新建节点添加
4)如果table[i]==null,不成立
4.1 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value
4.2 判断table[i]是否为treeNode(红黑树),如果是红黑树,则直接在树中插入键值对
4.3 遍历table[i],链表的尾部插入数据,然后判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,遍历过程中若发现key已经存在直接覆盖value
5)插入成功后,判断实际存在的键值对数量size是否超过了最大容量threshold(数组长度*0.75),如果超过,进行扩容(resize)。
4.讲一讲hashmap的扩容机制
1)在添加元素或者初始化的时候,都需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次扩容都是达到了扩容阈值(数组长度*0.75,也就是12);
2)大于12的时候进行扩容,每次扩容的时候,都是扩容之前容量的2倍;
3)扩容之后,会新创建一个数组,需要把老数组中的数据挪动到新数组中。
(3.1)没有产生hash冲突的节点,则直接使用 e.hash&(newCap - 1) 计算新数组的索引位置。
(3.2)如果是红黑树,走红黑树的添加
(3.3)如果是链表,则需要遍历链表,可能需要拆分链表,判断(e.hash&oldCap)是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上。
ps: oldCap:旧的容量 newCap:新的容量
5.hashmap的寻址算法
难易程度:4
出现频率:4
1)计算对象的hashCode()
2)再进行调用hash()方法进行二次哈希,hashcode值右移16位(扰动算法)再异或运算,让哈希分布更为均匀,减少哈希冲突。
3)最后(capacity - 1)&hash得到索引(代替了取模,效率更高一些)
6.为啥hashmap的数组长度一定是2的n次幂?
1)计算索引时效率更高:如果是2的n次幂可以使用位与运算代替取模;
2)扩容时重新计算索引效率更高;hash & oldCap ==0的元素留在原来位置 ,否则新位置 = 旧位置 + oldCap