HashMap在JDK8和JDK11的容量计算算法tableSizeFor分析

默认初始容量是16. HashMap 的容量必须是2的N次方, HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方, 例如传 9, 容量为16,

JDK8的计算代码:

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    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;
}

经过5个无符号右移的计算, 得到的值是一个低位全是1的值, 最后返回的时候 +1, 就会得到1个比n 大的 2 的N次方.

开头的 cap - 1 , 这是为了处理 cap 本身就是 2 的N次方的情况.如当cap=16时, 计算结果是32, 而cap-1=15的计算结果是16.

如, cap=30时, 经过5次 >>> 和 |= 运算后, 得到的值为 :

0001 1111

其对应的值为31, 31+1 = 32

JDK11中的代码:

static final int tableSizeFor(int cap) {
    int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

Integer.numberOfLeadingZeros 是算出cap-1二进制中前导0的个数, 如cap=14时, 算出前导0个数为28.

负数在计算机中是以补码的形式来表示的, -1的二进制为32个1. 对32个1无符号右移28位后,得到低位4个1的数,其十进制为15, 最后再+1就是16.

0000 0000 0000 0000 0000 0000 0000 1111       -> 15

而使用cap-1去计算前导0个数的原因也跟上面是一样的.

两者性能比较

从性能角度分析,JDK8所做的5次无符号右移运算, 在cap较小时, 可能做了很多次无用功, 如当cap=15时, 5次右移|=的计算结果都是1111, 白白多做了4次运算.

而numberOfLeadingZeros()方法内部采用了二分法的思想, 快速得到了前导0的个数.

下面例子分别调用JDK8和11的tableSizeFor()方法10亿次, 打印出分别的用时.

static final int MAXIMUM_CAPACITY = 1 << 30;

public static void main(String[] args)
{
    long start1 = System.currentTimeMillis();
    for (int i = 1; i < 1000000000; i++)
    {
        tableSizeFor8(i);
    }
    long end1 = System.currentTimeMillis();
    System.out.println("JDK8, 计算用时:" + (end1 - start1) + "ms");

    long start2 = System.currentTimeMillis();
    for (int i = 1; i < 1000000000; i++)
    {
        tableSizeFor11(i);
    }
    long end2 = System.currentTimeMillis();
    System.out.println("JDK11, 计算用时:" + (end2 - start2) + "ms");
}

static final int tableSizeFor8(int cap) {
    int n = cap - 1;
    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;
}

static final int tableSizeFor11(int cap) {
    int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

输出结果为:

JDK8, 计算用时:1522ms

JDK11, 计算用时:793ms

可以看到JDK11的算法几乎快了一倍! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值