【Java集合篇】为什么HashMap的Cap是2^n,如何保证?

在这里插入图片描述

为什么HashMap的Cap是2^n,如何保证?

  • ✔️目录
    • ✔️ 为什么是2 ^ n ?
      • ✔️为什么 X %2^n = X & (2^n - 1) ?
    • ✔️如何保证
    • ✔️初始化时期保证
    • ✔️扩容时期保证


✔️目录


✔️ 为什么是2 ^ n ?


HashMap是通过 (table.length - 1) & (key.hashCode ^ (key.hashCode >> 16)) 定位 tablelndex 的。


为什么是用 & 而不是用 % 呢 ? 因 为 & 是基于内存的二进制直接运算,比转成十进制的取模快的多。又因为 X % 2^n = X & (2n - 1),可以把%运算转换为&运算。所以,hashMapcapacity 一定要是 2^n。这样,HashMap计算hash的速度才够快。


✔️为什么 X %2^n = X & (2^n - 1) ?


假设n为3,则2^3 = 8,表示成2进制就是1000。2^3 -1 = 7,即0111。


此时X &(2^3 - 1)就相当于取X的2进制的最后三位数。


从2进制角度来看,X/8相当于 X >>3,即把X右移3位,此时得到了X/8的商,而被移掉的部分(后二位),则是X % 8,也就是余数。


上面的解释不知道你有没有看懂,没看懂的话其实也没关系,你只需要记住这个技巧就可以了。或者你可以找几个例子试一下。


如: 6%8 = 6,6&7= 6 10 %8 = 2,10&7 = 2


✔️如何保证


要想保证 HashMap 的容量始终是2^n次方,需要在Map初始化的时候,和扩容的时候分别保证。


/**
*    当我们在实现HashMap时,为了保持其容量始终为2的幂,我们可以重写其resize方法,在每次扩容时调整容量为最接近的2的幂。
*/
public class MyHashMap<K, V> extends HashMap<K, V> {  private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 16, which is 2^4  private static final float DEFAULT_LOAD_FACTOR = 0.75f;  @Override  protected void putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict, Node<K,V> node) {  // 在这里添加初始化时的逻辑  System.out.println("Putting key: " + key + ", value: " + value);  super.putVal(hash, key, value, onlyIfAbsent, evict, node);  }  @Override  protected Node<K,V>[] resize() {  // 获取当前容量  int oldCapacity = this.capacity;  // 计算新的容量,为最接近的2的幂  int newCapacity = Integer.highestPowerOfTwo(oldCapacity * 2);  // 调用父类的resize方法  return super.resize();  }  
}

✔️初始化时期保证


当我们通过 HashMap(int initialCapacity)设置初始容量的时候,HashMap并不一定会直接采用我们传入的数值,而是经过计算,得到一个新值,目的是提高hash的效率。(1 -> 1、3`-> 4、7 -> 8、9 -> 16)


在JDK 1.7和JDK 1.8中,HashMap初始化这个容量的时机不同。JDK 1.8中,在调用HashMap的构造函数定义HashMap的时候,就会进行容量的设定。而在JDK 1.7中,要等到第一次put操作时才进行这一操作。


看一下JDK是如何找到比传入的指定值大的第一个2的幂的:


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;n |= n >>>32;n |= n >>>64;n |= n >>>128;n |= n >>>256;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

上面的算法目的挺简单,就是: 根据用户传入的容量值(代码中的cap),通过计算,得到第一个比他大的2的幂并返回。


在这里插入图片描述

请关注上面的几个例子中,标记颜色字体部分的变化情况,或许你会发现些规律。

5->8、9->16、19->32、37->64 都是主要经过了两个阶段


Step 1,5->7
Step 2,7->8
Step 1,9->15
Step 2,15->16
Step 1,19->31
Step 2,31->32


对应到以上代码中,Step1:


	n |= n >>>1;n |= n >>>2;n |= n >>>4;n |= n >>>8;n |= n >>>16;

对应到以上代码中,Step2:


return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

Step 2 比较简单,就是做一下极限值的判断,然后把Step 1得到的数值+1。


Step 1 怎么理解呢?其实是对一个二进制数依次无符号右移,然后与原值取或。其目的对于一个数字的二进制,从第一个不为0的位开始,把后面的所有位都设置成1。


因为cap是int类型的,所以最多需要右移16位即可获取其最大值


随便拿一个二进制数,套一遍上面的公式就发现其目的了:
1100 1100 1100 >>>1 = 0110 0110 0110
1100 1100 1100 | 0110 0110 0110 = 1110 1110 1110
1110 1110 1110 >>>2 = 0111 1011 1011
1110 1110 1110 | 0011 1011 1011 = 1111 1111 1111
1111 1111 1111 >>>4 = 1111 1111 1111
1111 1111 1111 |  1111 1111 1111 = 1111 1111 1111

通过几次无符号右移和按位或运算,我们把1100 1100 1100转换成了1111 1111 1111,再把1111 1111 1111加1,就得到了1 0000 0000 0000,这就是大于1100 1100 1100的第一个2的幕。


好了,我们现在解释清楚了Step 1和Step 2的代码。就是可以把一个数转化成第一个比他自身大的2的次幂。


但是还有一种特殊情况套用以上公式不行,这些数字就是2的幂自身。如果cap=4套用公式的话。得到的会是 8,不过其实这个问题也被解决了,重点就在 int n = cap - 1; 这行代码中,HashMap会事先将用户给定的容量-1,这样就不会出现上述问题了。


总之,HashMap根据用户传入的初始化容量,利用无符号右移和按位或运算等方式计算出第一个大于该数的2的幕。


✔️扩容时期保证


除了初始化的时候会指定HashMap的容量,在进行扩容的时候,其容量也可能会改变。


HashMap有扩容机制,就是当达到扩容条件时会进行扩容。HashMap的扩容条件就是当HashMap中的元素个数 (size) 超过临界值 (threshold) 时就会自动扩容。


在HashMap中, threshold = loadFactor * capacity


loadFactor是装载因子,表示HashMap满的程度,默认值为0.75f,设置成0.75有一个好处,那就是0.75正好是3/4,而 capacity 又是2的幕。所以,两个数的乘积都是整数。


对于一个默认的HashMap来说,默认情况下,当其size大于12(16*0.75)时就会触发扩容。


下面是HashMap中的扩容方法(resize)中的一段:
if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) {newThr = oldThr << 1: // double threshold
}

因为oldThr已经是2^n了, 所以oldThr无符号左移之后,是oldThr*2,自然也是2^n。至于后续HashMap是如何扩容的,请听下回分解~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/327306.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【AIGC-图片生成视频系列-7】MoonShot:实现多模态条件下的可控视频生成和编辑

目录 一. 贡献概述 二. 方法详解​编辑 三. Zero-Shot主题定制视频生成 四. 文本到视频生成 五. 直接使用图像ControlNet 六. 图像动画比较 七. 视频编辑 八. 针对视频生成中多模态 Cross-Attn的消融实验 九. 对视频生成中多模态 Cross-Attn的消融实验 十. 论文 十一…

java数据结构与算法刷题-----LeetCode62. 不同路径

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 很多人觉得动态规划很难&#xff0c;但它就是固定套路而已。其实动态规划只…

强化学习1——多臂老虎机(上)

在强化学习中&#xff0c;关注智能体在与环境的交互中学习&#xff0c;成为试错型学习。多臂老虎机不存在状态信息&#xff0c;只有动作和奖励&#xff0c;是最简单的“和环境交互中学习“。 什么是多臂老虎机 老虎机_百度百科 (baidu.com) 多臂老虎机即有多个摇杆的老虎机&a…

[python]gym安装报错ERROR: Failed building wheel for box2d-py

报错截图&#xff1a; box2d是一个游戏领域的2D图形C引擎&#xff0c;用来模拟2D刚体物体运动和碰撞。 swig是一个将c/c代码封装为Python库的工具&#xff08;是Python调用c/c库的一种常见手段&#xff09;&#xff0c;所以在运行时box2d会依赖到swig。而swig并不是一个python库…

vivado xsim 终端 模拟

只模拟的话直接终端运行会快很多 计数器举例 mkdir srccounter.v module counter(input wire clk,input wire rst_n,output reg[31:0] cnt ); always (posedge clk or negedge rst_n)if(!rst_n)cnt < 31h0;elsecnt < cnt1;endmodule tb.v module tb; wire[31:0] out…

Guarded Suspension模式--适合等待事件处理

Guarded是被守护、被保卫、被保护的意思&#xff0c; Suspension则是暂停的意思。 如果执行现在的处理会造成问题&#xff0c; 就让执行处理的线程进行等待--- 这就是Guarded Suspension模式。 模式通过让线程等待来保证实例的安全性。 一个线程ClientThread会将请求 Request的…

MyBatis 源码分析(四):反射模块

前言 上一篇我们了解了Mybatis解析器模块&#xff0c;MyBatis 源码分析&#xff08;三&#xff09;&#xff1a;解析器模块 本篇我们来了解反射模块。相比 parsing 包来说&#xff0c;reflection 包的代码量大概是 2-3 倍。当然&#xff0c;不要慌&#xff0c;都是比较简单的代…

16、Kubernetes核心技术 - 节点选择器、亲和和反亲和

目录 一、概述 二、节点名称 - nodeName 二、节点选择器 - nodeSelector 三、节点亲和性和反亲和性 3.1、亲和性和反亲和性 3.2、节点硬亲和性 3.3、节点软亲和性 3.4、节点反亲和性 3.5、注意点 四、Pod亲和性和反亲和性 4.1、亲和性和反亲和性 4.2、Pod亲和性/反…

【Java技术专题】「攻破技术盲区」攻破Java技术盲点之unsafe类的使用指南(打破Java的安全管控— sun.misc.unsafe)

Java后门机制 — sun.misc.unsafe 打破Java的安全管控关于Unsafe的编程建议实例化Unsafe后门对象使用sun.misc.Unsafe创建实例单例模式处理实现浅克隆&#xff08;直接获取内存的方式&#xff09;直接使用copyMemory原理分析 密码安全使用Unsafe类—示例代码 运行时动态创建类超…

lombok注解 @Data使用在继承类上时出现警告解决

一、警告问题 1、Data注解 Data 包含了 ToString、EqualsAndHashCode、Getter / Setter和RequiredArgsConstructor的功能。 当使用 Data注解时&#xff0c;则有了 EqualsAndHashCode注解&#xff08;即EqualsAndHashCode(callSuperfalse)&#xff09;&#xff0c;那么就会在此…

Transformer-MM-Explainability

two modalities are separated by the [SEP] token&#xff0c;the numbers in each attention module represent the Eq. number. E h _h h​ is the mean&#xff0c; ∇ \nabla ∇A : ∂ y t ∂ A {∂y_t}\over∂A ∂A∂yt​​for y t y_t yt​ which is the model’s out…

新手小白必了解c语言之字符串函数

本篇介绍字符串库函数为 目录 引言 一&#xff1a;字符串函数的头文件为#include 二&#xff1a;求字符串长度函数 &#xff08;strlen&#xff09; 1.函数介绍 2.函数使用举例 3.模拟实现 三&#xff1a;字符串复制函数(strcpy) 1.函数介绍 2.函数使用举例 3.模…