Loading

为什么HashMap扩容是2倍以及容量为什么是2的n次幂

** java8**
为什么HashMap扩容是2倍以及容量为什么是2的n次幂,和这个数组下标的计算方法有着千丝万缕的关系。
先看看计算数组下标源码:

由上图我们可以看到,<key,value>要放到数组的那个位置,它会通过key的hash值和数组长度-1进行与运算来计算得出。也就是
(n - 1) & hash

这里n就是数组长度。 可以看出一旦数组扩容,算式中n就发生了变化,那么原来元素下标也会发生变化,<key,value>就要移动位置。
如果每一次扩容所有的<key,value>都要移动,势必带来性能上的不足。
但是当扩容是2倍,我们就会发现非常有趣的地方。
我们来看一下hashmap的扩容过程。
16(默认)-> 32(扩容一次) -> 64(扩容两次)
从10进制中我们或许发现不了什么,但是它是进行与运算所以咱们只要二进制,这时就有点意思了。

0001 0000    //16二进制    
0010 0000    //32二进制  
0100 0000    //64二进制

-1之后则变为

0000 1111     
0001 1111  
0011 1111 

比如现在我们有三个<key,value>要插入,通过计算key的hash值分别为

0101 1010 0011 1101
0101 1010 0011 1101
1110 0100 0001 0001

代入计算之后

hashCode(计算的hash值只用高16) size=16 size=32 是否移位
1010 0011 1101 1010 0011 1101&1111 1010 0011 1101&11111 向前移动16位
1010 0011 1101 1010 0011 1101&1111 1010 0011 1101&11111 不用移位
0100 0001 0001 0100 0001 0001&1111 0100 0001 0001&11111 向前移动16位

我们发现,扩容后是否移位,由扩容后key的hashcode参与计算的最高位是否1为所决定,并且移动的方向只有一个,即向高位移动。因此,可以根据对最高位进行检测的结果来决定是否移位,从而可以优化性能,不用每一个元素都进行移位,

是否移位,由扩容后表示的最高位是否1为所决定,并且移动的方向只有一个,即向高位移动。因此,可以根据对最高位进行检测的结果来决定是否移位,从而可以优化性能,不用每一个元素都进行移位。最高位为0 与运算之后的值依然与之前相同说明不用向高位移动。为1则需要向高位移动。

结论,原因有二:

第一哈希函数的问题 将元素充分散列,避免不必要的哈希冲突。
通过除留余数法方式获取桶号,因为Hash表的大小始终为2的n次幂,因此可以将取模转为位运算操作,提高效率,容量n为2的幂次方,n-1的二进制会全为1,位运算时可以充分散列,避免不必要的哈希冲突。
第二就是扩容时桶内元素是否向高位移动的问题。扩容为2倍,参与计算的hashcode高位为1则移动,为0则不移动,也就是说一个桶内元素只有一半的概率需要移动,从而优化了性能。

posted @ 2022-03-15 20:15  程序员小小宇  阅读(1555)  评论(0编辑  收藏  举报