HashMap容量为什么是2的n次方?如何做到的?

HashMap无疑是我们最常用的集合之一了,当我们调用HashMap(9)构建map的时候,它的容量是多少呢?

划重点!HashMap(9)构建的map的容量是16!!!这也就引出了下面的问题:

HashMap的容量为什么是2的n次方?HashMap是如何保证容量是2的n次方的?

HashMap容量取2的n次方,主要与hash寻址有关。在put(key,value)时,putVal()方法中通过i = (n - 1) & hash来计算key的散列地址。其实,i = (n - 1) & hash是一个%操作。也就是说,HashMap是通过%运算来获得key的散列地址的。但是,%运算的速度并没有&的操作速度快。而&操作能代替%运算,必须满足一定的条件,也就是a%b=a&(b-1)仅当b是2的n次方的时候方能成立。这也就是为什么HashMap的容量需要保持在2的n次方了。putVal部分代码如下:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // 此处省略了代码
        // i = (n - 1) & hash]
        if ((p = tab[i = (n - 1) & hash]) == null)
            
            tab[i] = newNode(hash, key, value, null);
        

        else {
            // 省略了代码
        }
}

那么,第二个问题“HashMap是如何保证容量是2的n次方的”,HashMap类中有个tableSizeFor方法,代码如下:

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;
}

从代码可以看出,n做了5次无符号右移(>>>)操作与或操作,那么它究竟发生了什么。其实,上述操作是将n对应的二进制数的最高位1后面全变为了1。为什么呢?看下面的例子:

hashmap容量能保持在2的n次方的原因

解释都写在上面的图上咯(不要在意字好不好看,哈哈哈~)。

码字不易,来个三连击吧~

 

推荐阅读:

这么回答面试官之--ConcurrentHashMap如何put?

这么回答面试官之--ConcurrentHashMap如何get?为何无需加锁

  • 30
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
2.7版(2020.4.7)     1) 调整 添加() 方法. 增加可选参数 参_不覆盖 (当键值存在时不进行值覆盖)     2) 添加 添加_指针() 方法. (具体用法参考使用例子)     3) 添加 cha询_指针() 方法. (具体用法参考使用例子)     4) 添加 有序模式,创建时可选参数.(此模式下  取所有键() 取所有值()  将按添加时的顺序来取出数组,时间复杂度: O(1).具体用法参考使用例子)     5) 添加 有序_插入() 方法. (有序模式下使用.)     6) 添加 有序_取序号() 方法. (有序模式下使用.)     7) 添加 有序_取序号键值() 方法. (有序模式下使用.通过遍历序号取键值,性能较低,单次取值时间复杂度: O(n),非必要不建议使用)     8) 说明 枚举键值还是乱序枚举,因为即便是有序模式xx的储存依然是无序的.可使用 取所有键() 取所有值() 存到数组来遍历. 2.6版(2020.3.31)     1) 修复 值运算() 方法 异常问题. (感谢 精易论坛  【qq1347522182】 的反馈)     2) 添加 寻找文本键 方法(功能类似 是否包含文本键 方法,多2个参数【参_开始位置】【参_返回键名长度】).     3) 调整 创建() 方法 添加参数 参_不使用内存池 (默认为假(使用内存池), 为真则不使用内存池(xx量不大时可以选择不使用内存池以节省内存使用))     4) 优化 自定义xx_模版 类 (优化后无需对自定义xx成员进行引用,只要将模版内的【自定义xx】类型 全部更改成 所需的类型即可) 2.5版(2020.1.11)     1) 修复 模块初始化优先级问题导致的异常. 2.4版(2019.12.16)     1) 优化 内部添加内存池,使用私有堆申请内存时改为内存池来分配内存,添加() 和 载入表() 性能得到显著提升。 2.3版(2019.12.9)     1) 修复 取值的键数组 逻辑值获取失败问题。     2) 调整 长整数键的哈希计算调整为单独的方法调用,减少些汇编代码。
### 回答1: hashmap的size之所以必须是2的整数次方,是因为hashmap内部使用了一个数组来存储元素,而数组的长度必须是2的整数次方,才能在计算元素存储位置时得到更好的性能表现。这是由于hashmap的hash函数计算得到的哈希值是一个整数,当数组长度为2的整数次方时,只需对哈希值进行与操作,就能得到元素在数组中的索引,这个操作比取模运算要快很多。同时,长度为2的整数次方的数组也具有更好的缓存性能,能够更充分地利用计算机硬件的优势。 ### 回答2: HashMap 是基于哈希表实现的键值对映射数据结构,其底层使用一个数组来存储键值对。在使用 HashMap 时,需要指定其初始容量和加载因子,其中初始容量即数组的长度,加载因子默认为 0.75。当哈希表中的元素数量超过了容量乘以加载因子时,哈希表会自动动态扩容。此时,原来的数组将会被复制到新的长度为原来两倍的数组上。在 HashMap 进行哈希映射时,会使用对象的 hashCode() 方法来计算其在哈希表中的索引位置。而 hashCode() 方法返回的整型值需要将其模以数组长度才能得到真正的索引位置。 因此,如果 HashMap 的初始容量不是 2 的整数次方,那么在动态扩容时计算新数组的长度时就无法简单地进行位运算,而需要使用更加复杂的除法运算,从而降低了计算效率。同时,在使用取模操作计算哈希表的索引位置时,如果数组长度不是 2 的整数次方,那么计算哈希值的低位部分和数组索引的低位部分就会出现重复情况,这可能会导致哈希表的性能下降,产生哈希冲突等问题。因此,为了避免这样的问题,HashMap 的初始容量必须是 2 的整数次方。 此外,因为数据存储在一段连续的内存空间中,系统可以通过缓存预读来提高访问速度。而在计算数组的索引位置时,使用位运算相比于其他运算方式更快。因此,使用 2 的整数次方来作为数组的长度可以充分利用计算机硬件的特性,提高 HashMap 的访问效率。 ### 回答3: 在Java中,HashMap是一种散列映射表,它可以将键值对存储在bucket中,通过键来快速定位值。HashMap的性能和内存空间使用率很大程度上依赖于HashMap容量大小。HashMap容量大小是指bucket的数量。在创建HashMap时,我们必须指定HashMap的初始容量大小。这个初始容量大小是HashMap的bucket数量。 HashMap内部的实现是将键值对分布在不同的bucket中。当一个键值对需要加入到HashMap中时,会根据其哈希值(hashCode)的高几位来选择其所属的bucket。为了加速哈希值的计算,Java中的哈希表选择使用2的整数次幂作为哈希表的长度,这样可以保证哈希值的高位分布更均匀。因为在计算哈希值时,常用的一种算法是“哈希值=HashCode(key)&(HashMap.size-1)”(其中&表示与运算),如果HashMap的size不是2的整数次幂,那么HashMap.size-1的任何位都可能为1或0,而不是10000……这样的形式,这就使得用相同的HashCode映射到不同的bucket中的概率增加了,可能导致性能下降,同时也增加了发生哈希冲突(多个键值对哈希值相同)的概率,引起内存的浪费。如果HashMap的size是2的整数次幂,那么HashMap.size-1的位上的数都是1,这样就可以保证HashCode&(HashMap.size-1)的结果落在[0,HashMap.size-1]的范围内,哈希函数相对均匀有效,同时内存的利用率也是最高的。 综上所述,HashMap的size必须是2的整数次幂,这样可以使得哈希函数更加均匀,性能更高,同时也能更好的利用内存。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

进击的Coder*

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

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

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

打赏作者

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

抵扣说明:

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

余额充值