HashMap 容量大小的问题-为什么长度都是2的幂?

8 篇文章 0 订阅
4 篇文章 0 订阅

前言

在之前的文章 我分析过HashMap 初始化容量的问题 不清楚的可以看这个。

经过这篇文章 我们知道了 HashMap是什么时候 设置容量大小的,容量大小和容量的阀值 是怎么计算的,但是有的小伙伴 包括我 可能对一点比较好奇 为什么默认的容量是16 而且计算是自己容量的时候,最终计算出来的容量也是2的幂次方?可能 有的小伙伴知道 这个是为了 降低哈希碰撞率,那是为什么呢?那我们今天就来聊一聊

分析

容量计算

    /**
     * Returns a power of two size for the given target capacity.
     */
    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;
    }

照顾下 没看之前的文章的小伙伴 还是回顾下

这个方法是Hashmap里面去计算初始容量需要用的 其目的就是获取一个大于当前传入的cap值的2的最小幂次方的数值。

看完这句话 感觉好拗口的 ,又是大于 又是小于的 比较懵逼,没事 那我们给出2个栗子:

  • cap:10 这个方法的到值是16 也就是2的4次方
  • cap:17 这个方法的到值是32 也就是2的5次方
  • cap:1000 这个方法的到值是1024 也就是2的10次方

我相信 看到了这个我相信 小伙伴们 一定知道了吧~

hash位置计算

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        ...
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        ...
    }       
    

上面是我截取的 计算HashMap 位置的代码

我们知道 HashMap 里面计算卡槽的位置 是经过了这么几步:

hash

首先 我们调用hash这个方法,这个方法 就是回去key的hascode值 然后和hashcode值的右移16位后 的值 做异或处理

为什么要这么处理呢?

首先我们知道异或【^】的逻辑运算的规则是 相同为0 不同为1 即:0 ^ 0=0,1 ^ 1=0,1 ^ 0=1,0 ^ 1=1

与【&】运算的规则 2者都为1 时候结果为1 否则为0 即:0&0=0, 1&1=1 ,1&0=0 ,0^1=0

顺便也会回顾下 或运算符【|】 规则是 只有有1 那结果就是1 即:0&0=0,1&1=1 ,1&0=1 ,0^1=1

记住下 上2个逻辑运算符 方法里面会涉及到

那我们再来回顾下 (h = key.hashCode()) ^ (h >>> 16) ,h >>> 16 这个操作就是 将二进制位往右移16位 空位的用0补足,知道了这个 我们来举个小demo 来看下 这个是要做什么,16为的花 我要写很长一段数组 那我就右移4位吧,举例说明下:

如果key的hashCode 的二进制值是:1011 0011 那么>>> 4的结果就是0000 1011 那么2者相【^】异或的结果是:

1011 0011

0000 1011

结果: 1011 1000

看到这样的结果是 高4位的数值 其实没有变化 低4位的数值 是有变化的,这样做的目的是把高4位的影响 降低到低4位中,保证hash后面hash值 在计算位置的时候 减少hash的冲突,具体 可以看下 英文的注释,翻译过来大概就是这个意思!

(n - 1) & hash

这个是计算位置的方法 hash 值就是上个方法中 传入过来的

如果这个是的hash值是 1011 1000 我们的n值就是16,那么就算结果将是这样的
hash: 1011 1000

n-1 : 0000 1111

结果: 0000 1000 10进制数值是:8

如果这个时候改变hash值是 1011 1100 那么就算结果将是这样的
hash: 1011 1100

n-1 : 0000 1111

结果: 0000 1100 10进制数值是:12

这个时候 我们看到 随着我们的hash值的不同 的到的index 也是不同的 ,这样能保证hash值 能均分的分配

但是如果这个是 我们的n长度不是2的幂次方,比如是10 ,那么计算结果是这样的
hash: 1011 1000

n-1 : 0000 1001

结果: 0000 1000 10进制数值是:8

这个时候 我们还是修改 hash值是 1011 1100 那么就算结果将是这样的
hash: 1011 1100

n-1 : 0000 1001

结果: 0000 1000 10进制数值是:8

这个时候 我们发现 虽然hash值修改了 但是 我们计算得到的index 还是相同的,就那上面这个例子 如果hash值是 1011 1000, 1011 1100, 1011 1110 计算得到的结果 都是 0000 1000 index 都是8 这样会提高Hash的冲突率,这显然不是我们想要的。

看到这里 你是否 知道了 为什么都是2的幂次方了么, 因为2的幂次方减掉1后, 二进制的值的 低位都是1,这样就保证了和Hash值 相与【&】后结果 低位都是被保留下来的 这样就可以避免不同的Hash值传入进来 被分配到 同一个卡槽位置 去存储。

结论

通过以上的分析 你是否清楚了呢。不清楚的话 多看看 多想想 ,设计是如此的其妙!一个细节都是那么的优秀,值得我们去深究 和学习。

废话不多说 ,总结一下 hashMap中容量的数值都是2的幂次方 是为了降低hash的冲突,那位什么能降低hash的冲突呢,是因为2的幂次方-1 后的数组的二进制 的低位 全部都是1,这样保证了和hash值与【&】之后 能够完整的保留hash值的低位 这样就避免了不同的hash值过来被分配到一样的hash卡槽中!

总计完毕!!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值