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

    点击上方 "程序员小乐"关注, 星标或置顶一起成长

每天凌晨00点00分, 第一时间与你相约

每日英文

There are plenty of things in life that you don't want to do but you have to,this is responsibility.For the things that you want to do but you can't,this is fate.

生命中有许多你不想做却不能不做的事,这就是责任;生命中有许多你想做却不能做的事,这就是命运。

每日掏心

有一个夜晚我烧毁了所有的记忆,从此我的梦就透明了;有一个早晨我扔掉了所有的昨天,从此我的脚步就轻盈了。

来自:Helloworld先生 | 责编:乐乐

链接:blog.csdn.net/u010841296/article/details/82832166

程序员小乐(ID:study_tech)第 768 次推文   图片来自 Pexels

往日回顾:重大突破!这两种药能抑制冠状病毒,上市公司连拉四个涨停板!疫情攻坚战将迎来曙光?

   正文   

HashMap是根据key的hash值决策key放入到哪个桶(bucket)中,通过 tab=[(n - 1) & hash] 公式计算得出,其中tab是一个哈希表。

1. 为什么要保证 capacity 是2的次幂呢?

1)在get方法实现中,实际上是匹配链表中的 Node[] tab 中的数据。

(n - 1) & hash实际上是计算出 key 在 tab 中索引位置,当key的hash没有冲突时,key在HashMap存储的位置就是匹配的node中的第一个节点。如果hash有冲突,就会在node里面节点中查询,直至匹配到相等的key。

2)因为 n 永远是2的次幂,所以 n-1 通过 二进制表示,永远都是尾端以连续1的形式表示(00001111,00000011)

当(n - 1) 和 hash 做与运算时,会保留hash中 后 x 位的 1

例如 00001111 & 10000011 = 00000011

这样做有2个好处

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

  • 能保证 索引值 肯定在 capacity 中,不会超出数组长度

  • (n - 1) & hash,当n为2次幂时,会满足一个公式:(n - 1) & hash = hash % n

2.为什么要通过 (n - 1) & hash 决定桶的索引呢?

1)key具体应该在哪个桶中,肯定要和key挂钩的,HashMap顾名思义就是通过hash算法高效的把存储的数据查询出来,所以HashMap的所有get 和 set 的操作都和hash相关。

2)既然是通过hash的方式,那么不可避免的会出现hash冲突的场景。hash冲突就是指 2个key 通过hash算法得出的哈希值是相等的。hash冲突是不可避免的,所以如何尽量避免hash冲突,或者在hash冲突时如何高效定位到数据的真实存储位置就是HashMap中最核心的部分。

3)首先要提的一点是 HashMap 中 capacity 可以在构造函数中指定,如果不指定默认是2 的 (n = 4) 次方,即16。

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

4)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

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

3.capacity 永远都是 2 次幂,那么如果我们指定 initialCapacity 不为 2次幂时呢,是不是就破坏了这个规则?

答案是:不会的,HashMap 的tableSizeFor方法做了处理,能保证n永远都是2次幂。

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

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。

欢迎各位读者加入订阅号程序员小乐技术群,在后台回复“加群”或者“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

一种前端灰度发布方案

高并发下的抽奖优化

Git 如何优雅地回退代码

关注订阅号「程序员小乐」,收看更多精彩内容

嘿,你在看吗

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值