HashMap中的算法魅力之初始容量计算——小细节,大智慧

HashMap中的算法魅力之初始容量计算

懒人先看:HashMap用翻倍位移低位补1算法实现了初始容量计算。
转载请注明出处,您的支持是我坚持不懈的动力源泉~

初始化容量的计算

  • 找到比当前数值大的最小2次幂,入参cap是二进制的用户输入的容量;
static final int tableSizeFor(int cap) {
        int n = cap - 1;//为啥要减去1?cap代表的是数量,减1后就能知道存储对应cap数量二进制所需的位数
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

为什么要依次位移1、2、4、8、16位呢?

hash值是int类型的,长度为32。我们举个例子:1010 1100 1110 0010
1、向右位移1位,则可以保证原来不为0最高位的相邻位为1,或运算后在保留最高位的前提下,把次高位也变成了1。第一步保证了不为0的前2位或运算结果为1。
2、接下来,位移2位,可以保证前4位有效位全为1;
3、接下来,位移4位,可以保证前8位有效位全为1;
4、再次位移16位,最终保证最高位向右的位全部为1。
图示:
在这里插入图片描述
结果再加上数值0,就是初始化容量的最大值了。

这个算法是不是还能优化呢?

我们可以看到,低位参与的或运算的结果并不算有效计算,因为最终结果被舍弃了,即位移过程中有最高位就够了。我在这里斗胆把它称作为低位补1算法。那我们是不是发现了该算法还有优化的余地呢?恰巧,我在阅读ConcurrentHashMap源码的时候,看到了这样一个算法:

	int ssize = 1;
    while (ssize < DEFAULT_CONCURRENCY_LEVEL) {
        ++sshift;
        ssize <<= 1;
    }

应用在HashMap这里是不是可以把位移这块代码改成这样呢?

static final int tableSizeFor(int cap) {
    int ssize = 1;
    while (ssize < cap) {
        ssize <<= 1;
    }
}

嘻嘻一阵窃喜,有木有~
然而,并非如此……
接下来我们把这两种算法分别叫做“原生低位补1算法”、“比较位移算法”,二者的区别其实很明显了。原生算法不管用户的入参cap值有多大,仍然进行了5次位移或运算;比较位移算法则从小到大逐位位移,如果cap值很大,位移+比较的次数就会很大了!也就是值小的时候可以采用第二种算法。

最大容量的计算

我们再看一下HashMap是怎么定义最大容量的?

/**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

二进制表示为:0100000000000000000000000000000,也就是2^30。
请注意,容量的返回值是int类型,按照第一节中提到的算法,最高位起低位补1的结果+1就是初始容量!基于此算法,我们的最高位是不是就不能为1了呢,否则就会造成数值溢出!

总结

HashMap的容量计算用到的是低位补1算法,并巧妙的用到了翻倍位移。

猜你喜欢

转载自blog.csdn.net/l714417743/article/details/105465371