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

1014

被折叠的 条评论
为什么被折叠?



