HashMap初始容量为何是2的n次方

HashMap初始容量为何是2的n次方

HashMap创建可以不指定初始容量,也可以指定初始容量。未指定时,默认容量为16,这在HashMap源码中表明:

image-20220612220943585.png

所以,当我们使用无参构造实例化HashMap对象时,初始容量threshold会赋值为16。但是,当我们指定初始容量创建HaspMap实例时,threshold会是赋值为我们所设置的初始值吗?答案是不一定。因为在指定初始容量创建,对threshold赋值时进行了这样一步操作:

image-20220612221517471.png

对threshold赋值调用了tableSizeFor()方法。这个方法是对我们指定的初始容量转换成大于等于这个容量的2的n次方的最小数。原理是bit位的无符号右移运算。该方法如下:

image-20220612221927623.png

这里补充下bit位的表示形式,int类型4字节,而一字节占8bit,故int占32个bit位,如下int型20的bit表示:

        //int占32bit位,即20=2^4+2^2
        //即 0000 0000 0000 0000 0000 0000 0001 0100
        System.out.println(Integer.numberOfLeadingZeros(20));
复制代码
Integer.numberOfLeadingZeros(num);
复制代码

这个方法是计算除前导零的个数,20的前导零的个数为27。

image-20220612222417712.png

在tableSizeFor()方法中int n = -1>>>Integer.numberOfLeadingZeros(cap-1)表示对-1无符号右移cap-1的前导零个数位。

为什么使用负一呢?因为bit位表示负数时,最高位为符号位,则负一的表示为:

//-1 = 1000 0000 0000 0000 0000 0000 0000 0000
复制代码

刚好最高位为1,其余为零,等于2的31次方,位移的结果也是2的n次方。

为什么是cap-1不是cap的前导零的个数呢?因为这是避免当cap等于2的n次方时,位移结果变成2的n+1次方,如下代码结果:

        //-1 = 1000 0000 0000 0000 0000 0000 0000 0000
        //无符号右移27位:0000 0000 0000 0000 0000 0000 0001 0000 = 31
        System.out.println(-1>>>Integer.numberOfLeadingZeros(32-1));
        System.out.println(-1>>>Integer.numberOfLeadingZeros(32));
复制代码

image-20220612223934623.png

因此,减一是为了保证临界值cap等于2的n次方时,右移的结果等于本身。

最终,tableSizeFor方法返回n+1的结果,刚好为大于等于cap的最小的2的n次方数。则使用初始容量实例化hashmap的初始值大于等于cap,为2的n次方数。

综上所述,HashMap默认初始值始终是2的n次方。但为什么HashMap要这样设计初始值呢?

image-20220612225316301.png

原因在这,HashMap存储元素时下标计算是使用数组长度减一和key计算出的hash值进行按位与运算。

为什么要使用这样的设计呢?

我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是 2 的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是 2 的幂次方。

因此,HaspMap的初始容量为2的n次方是为了满足数组下标计算,提高运算效率,进而提高HashMap的存取效率。

以上是个人的理解,如果有不对的地方请指正,谢谢。

猜你喜欢

转载自juejin.im/post/7108371673870499854