在JDK1.8中,HashMap无论是默认容量还是自定义容量最终的容量都是2的幂次方。
比如在创建一个HashMap时指定了初始容量为14,HashMap会向上取一个相邻的2的幂次方,比如14相邻的就是16,那么最终我们指定14大小容量的HashMap他最终的容量其实是16,扩容时也是容量*2的进行扩容。他为什么非要取一个2的幂次方作为容量呢?默认初始容量都是16
在HashMap中put方法中会负责给当前key值计算一个hash槽位,网上很多教程都说是取模,但是我看源码的过程中并没有发现取模(%)的操作,并发现他采用的是 & (按位与运算)
首先介绍一下按位与运算,把两个十进制的数值转为二进制后,两个二进制进行按位与运算,二进制也就是010101这种的只有0和1组成的,两个数值按位比较,都为1的比较结果后为1,有一个为0比较结果就为0。
举一个简单的例子,比如目前有两个整数,4 和 2 进行按位与运算
4二进制转换后为:000100 ,2二进制转换为000010,
000100和 000010 按位与运算之后是 000000,从右向左按位比较两个都为1则结果为1,有一个为0则结果为0。
我们可以在上面的图片中看到hashmap寻找槽位首先是 整个容量的长度 - 1 在和 key算出来的hash进行按位与运算,那么这个数值算出来一定是在 容量长度范围之内吗?他一定要保证在范围之内,要不然就下标越位了。
他保证的前提条件就是这个容量的长度必须是 二的幂次方,因为只有二的幂次方 -1 之后的数值转为二进制所有低位都为1,比如16-1的结果是15,15的二进制是001111,那么低位都为1,再比如32-1的结果是31,31的二进制是011111,所有的低位都为1。
那么任何一个二进制数据(比较数)和低位都为1的数据(被比较数)进行按位与运算结果 会保留比较数的低位,比如被比较数的二进制是4个1,那么任何二进制数值(比较数)和被比较数进行按位与运算后,保留的结果是和比较数的4个低位一摸一样的,也可以这么理解,二进制从右往左数 4个数之后的数据全被抹除了,因为被比较数的 二进制只有4位,其他全为0,就算比较数有1那么最终按位与运算之后都为0。还不理解的我举个例子
比如说:
hashmap的容量是16,算key的槽位时 进行 16-1 = 15
15转为二进制后为1111
key的二进制假设为1011110010101110
两个值进行按位与运算,最终结果1110,只保留了key值的4个低位(从右往左数)
那么按位与运算之后的值肯定是比15小的,那么肯定能找到某个槽位,就比如说当前例子算出的槽位就是14。
那么就明白了hashmap的槽位是二的幂次方,因为只有二的幂次方-1的数值的二进制低位都为1,其他数值转为二进制后都不为1,理解了之后就会很清晰了