hashmap为什么长度要是2的n次幂

1、使用位运算(&)来实现取模运算(%)

在jdk1.7源码中,hashmap中有一方法,叫做indexFor(),该方法其实主要是将hash生成的整型转换成链表数组中的下标。我们理所应当的应该认为会使用key的hash值对着数组总长度取模得到所引下标,但它的代码是这样的:

return h & (length-1)

那这段代码是什么意思呢?其实,他就是取模。Java之所有使用位运算(&)来代替取模运算(%),最主要的考虑就是效率。位运算(&)效率要比代替取模运算(%)高很多,主要原因是位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。

X % 2^n = X & (2^n – 1)

2n表示2的n次方,也就是说,一个数对2n取模 == 一个数和(2^n – 1)做按位与运算 。

假设n为3,则2^3 = 8,表示成2进制就是1000。2^3 = 7 ,即0111。

此时X & (2^3 – 1) 就相当于取X的2进制的最后三位数。

从2进制角度来看,X / 8相当于 X >> 3,即把X右移3位,此时得到了X / 8的商,而被移掉的部分(后三位),则是X % 8,也就是余数。

6 % 8 = 6 ,6 & 7 = 6

10 & 8 = 2 ,10 & 7 = 2
在这里插入图片描述

所以,return h & (length-1);只要保证length的长度是2^n的话,就可以实现取模运算了。而HashMap中的length也确实是2的倍数,初始值是16,之后每次扩充为原来的2倍。

2、扩容时,元素的位置迁移

我们知道,hashmap在元素个数达到 元素个数阈值*加载因子 的时候,会进行扩容操作,扩容一定意味着之前的元素需要发生位置的迁移。

而面对元素的迁移,结合1,我们不难想到用来替代取模运算的&运算重新对每一个元素进行重新散列计算所在索引位置。

但实际上,这种操作也并非是最优解,还有什么办法呢? 那就是只需看该元素的hash值二进制位上与此次扩容后长度-1(2^n-1)的二进制位上的最高位对应的那位是0还是1,如果是1,则不需要变化,因为1 & 1还是1,如果是0 ,则只需要让索引值+新多出来的那1位的值。

举个栗子:

有一个元素X,hash后的值为29,二进制为: 0001 1101
所要放入的map的内部数组长度为16,加载因子为0.75
此时根据上述位运算求索引的方法,使用hash(X) & 数组.length-1,
29==> 0001 1101
&
16-1=15==> 0000 1111
0000 1101
由此可以得出,求出的索引值为13

当随着元素的不断增加,达到了数组扩容的阈值: 16x0.75=12
此时,对数组进行2倍扩容,变成长度为32的数组。
然后将对原有元素进行移动位置,以上述X为例,

先使用我们2中总结出来的快捷算法来进行计算:

原索引值为13
32比16在二进制上多出的那一位,也就是16

此时,我们算出X元素在扩容后的位置应当是 13+16=29

接下来,我们使用重新&运算来进行计算,
12==> 0001 1101
&
32-1=31==> 0001 1111
0001 1101
由此可以得出,求出的索引值为29

并且从&运算的运算过程中也可以看到,X原有位上(后4位)数值不管是和16比还是和32比,都是没有变化的,因为不管是16还是32,这几位上面都是1。
而唯一可能会发生变化的,则是取决于X对应的新一位上的数是0还是1,是0的话,数压根就不会变。 是1的话, 只需要原有值+加上这多出来的1位的值就可以得到新的散列值了

但不得不提,这一切,都要得益于只有长度在2的n次幂的情况下才能够使用。

那hashmap里是怎么应用的呢?
关于2的源码部分:
在这里插入图片描述

在jdk1.8中的hashmap源码中,就运用了这个技巧来对resize后的元素索引重排进行优化。
核心代码是:e.hash & oldCap== 0
通过该逻辑将旧数组里的每一个链表元素分为两部分,上述代码==0和!=0的,之后 ==0的在新数组的位置不变,!=0的 用原有索引值+老的数组长度 得到的索引就是它的新位置

那为什么可以用这个逻辑来决定元素到底需不需要改变索引值呢?通过之前的描述,我们其实只需要知道扩容后新增的那位上,该元素的值到底是0还是1, 那如何得到这个值?
e.hash & oldCap就可以得到!
也就是 元素hash &原有数组的长度
此时,数组长度二进制位上,低位都为0,高位为1
此时,元素的hash的与高位的对应位与高位进行&运算,自然知道是0还是1!

沿用之前的例子,有一个元素X,hash后的值为29(e.hash),二进制为: 0001 1101
所要放入的map的内部数组长度为16(oldCap)
此时使用上面的算法,过程:
29==>0001 1101
16==>0001 0000
0001 0000
可知结果是1。

参阅:深入理解hashmap

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值