1.扰动算法中为什么要右移16位然后和原数据进行异或运算?
答:因为在下一步的寻址算法((n - 1) & hash)中与运算会舍弃掉高位数据,原因是平常大部分的hashmap的数组长度都比较小,这样高位不参与运算就会增加碰撞概率。其次,之所以选择异或运算而不是与运算或者或运算的原因是:二进制中,理论上0和1出现的概率都是1/2,与运算有3/4的概率为0,1/4的概率为1,或运算刚好相反,异或运算各为1/2比较均衡,所以要使用异或运算。
2.扰动算法具体是怎么做的?
答:(h = key.hashCode()) ^ (h >>> 16) 右移16位,舍弃低位高位补0,然后和原数据异或运算,这样原数据高16位和0进行异或运算结果保持不变,原数据低16位和新数据低16位(即原数据高16位)异或运算,总体相当于高16位部分不变,低16位部分高低位混合。
3.链表转化为树的条件是什么?
答:当链表长度大于8(注意,不是大于等于7也不是大于等于8),并且数组长度大于等于64时链表转化为红黑树。
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//当binCount>=7时,链表长度已经是9了
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
如上代码所示,binCount时从0开始的,P已经有了一个节点(不为空),并且这行代码if (binCount >= TREEIFY_THRESHOLD - 1) 前面已经使用尾插法插入一个新的节点p.next = newNode(hash, key, value, null);,所以当这个判断成立时链表长度已经是9了,也就是大于8的由来。
另外在树化之前会判断if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY),由此可得数组长度必须大于等于64,否则就会优先去扩容。
4.HashMap允许空键空值么?
答:HashMap最多只允许一个键为Null,并且会将key为null的键值对放在数组第0位(多条会覆盖),但允许多个值为Null。
5.put流程是怎么样的?
答:
7.HashMap 的底层数组长度为何总是2的n次方?
答:这与源码采用的散列算法有关,源码中采用了h&(length - 1)进行散列运算,首先,按位与运算比取模更加高效,因为采用了按位&运算,而且2的幂减去一(n-1)的二进制数字都是1,只有这样的数在与运算时冲突最少数据分布更加均匀。
8.什么时候会进行扩容?
答:扩容时机有三个,
第一:
数组为空时
第二:
链表转为红黑树时:例如当插入元素后链表长度大于8并且数组长度小于64的时候会进行扩容
第三:
插入的元素总数量大于阈值时:即if (++size > threshold) resize();
9.扩容原理(jdk1.8):
答:由于数组的容量是以2的幂次方扩容的,那么一个Entity在扩容时,新的位置要么在原位置,要么在原长度+原位置的位置,
如上图,二进制上多了一个高位参与数组下标确定。此时,一个元素通过hash转换坐标的方法计算后,恰好出现一个现象:最高位是0则坐标不变,最高位是1则坐标变为“10000+原坐标”,即“原长度+原坐标”。
在源码中是这样判断的e.hash & oldCap == 0,原因在于数组长度即oldCap的二进制最高位是1,其余位是0。例如16的二进制为10000,假如e.hash的二进制最高位为0的话则
e.hash & oldCap的值肯定为0,反之则不为0。所以:
if(e.hash & oldCap == 0){
//坐标位置不变
}else{
//坐标位置=原坐标+原长度(oldCap)
}
10. 退化成链表
退化时机:原来是红黑树并且是在扩容时
在扩容的时候根据e.hash & oldCap == 0的结果重新分为高低两条链,对每一条链,如果链表长度小于等于6,则原来的树会退化为链表。