HashMap:为什么容量总是为2的n次幂

HashMap:为什么容量总是为2的n次幂

1)、HashMap是根据key的hash值决定key放到哪个桶中,通过tab[i = (n - 1) & hash]公式计算得出

这里的n是HashMap的容量,因为n永远是2的次幂,所以n - 1通过二进制表示,永远都是末尾连续1的形式表示(如00001111、00000011),当(n - 1) 和hash做与运算时,会保留hash中后x位的1

例如00001111 & 10000011 = 00000011

这样做的好处在于:

  • &运算速度快,至少比%取模运算快

  • 能保证索引值肯定在HashMap的容量大小范围内

  • (n - 1) & hash的值是均匀分布的,可以减少hash冲突

举个例子来看:

假设n = 16,hash从0开始递增:

hash(n-1)& hash结果
01111 & 00
11111 & 11
21111 & 102
31111 & 113
41111 & 1004
51111 & 1015
………………
161111 & 100000
171111 & 100011
181111 & 100102

假设n = 15,hash从0开始递增:

hash(n-1)& hash结果
01110 & 00
11110 & 10
21110 & 102
31110 & 112
41110 & 1004
51110 & 1014
………………
161110 & 100000
171110 & 100010
181110 & 100102

2)、那么问题来了,如果我们通过构造函数指定了initialCapacity不为2的n次幂时,是不是就破坏了这个规则?

答案是不会的,HashMap的tableSizeFor方法做了处理,能保证n永远都是2的n次幂,让我们通过源码来看:

    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        // 调用了tableSizeFor方法来指定桶大小
        this.threshold = tableSizeFor(initialCapacity);
    }
    static final int tableSizeFor(int cap) {
        // cap-1后,n的二进制最右一位肯定和cap的最右一位不同,即一个为0,一个为1,例如cap=17(00010001),n=cap-1=16(00010000)
        int n = cap - 1;
        // n = (00010000 | 00001000) = 00011000
        n |= n >>> 1;
        // n = (00011000 | 00000110) = 00011110
        n |= n >>> 2;
        // n = (00011110 | 00000001) = 00011111
        n |= n >>> 4;
        // n = (00011111 | 00000000) = 00011111
        n |= n >>> 8;
        // n = (00011111 | 00000000) = 00011111
        n |= n >>> 16;
        // n = 00011111 = 31
        // n = 31 + 1 = 32, 即最终的cap = 32 = 2 的 (n=5)次方
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

3)、hash算法

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

HashMap中的hash也做了比较特别的处理,(h = key.hashCode()) ^ (h >>> 16)

先获得key的hashCode的值 h,然后 h 和 h右移16位 做异或运算

实质上是把一个数的低16位与他的高16位做异或运算,因为在前面 (n - 1) & hash 的计算中,hash变量只有末x位会参与到运算。使高16位也参与到hash的运算能减少冲突

例如1000000的二进制是 00000000 00001111 01000010 01000000

右移16位: 00000000 00000000 00000000 00001111

异或 00000000 00001111 01000010 01001111

参考:https://blog.csdn.net/eaphyy/article/details/84386313

https://blog.csdn.net/u010841296/article/details/82832166

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邋遢的流浪剑客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值