HashMap扩容时的rehash方法中(e.hash & oldCap) == 0算法推导

简介: HashMap在扩容时,会创建一个新数组,并将旧数组中的数据迁移过去。通过(e.hash & oldCap)是否等于0,数据被巧妙地分为两类:一类保持原有索引位置,另一类索引位置增加旧数组长度。此过程确保了数据均匀分布,提高了查询效率。

PS:由于文档是我在本地编写好之后再复制过来的,有些文本格式没能完整的体现,故提供下述图片,供大家阅览,以便有更好的阅读体验:
image.png
HashMap在扩容时,需要先创建一个新数组,然后再将旧数组中的数据转移到新数组上来
此时,旧数组上的数据就会根据(e.hash & oldCap) 是否等于0这个算法,被很巧妙地分为2类:
① 等于0时,则将该头节点放到新数组时的索引位置等于其在旧数组时的索引位置,记为低位区链表lo开头-low;
② 不等于0时,则将该头节点放到新数组时的索引位置等于其在旧数组时的索引位置再加上旧数组长度,记为高位区链表hi开头high.
具体,详见下述的算法推导解析:
算法:
(e.hash & oldCap)=0
前提:
 e.hash代表的是旧数组中节点或元素或数据e的hash值,该hash值是根据key确定过的:(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16) ;
 oldCap为旧数组的数组长度,是2的n次幂的整数。即e.hash&2^n=0

推导过程1(e.hash & oldCap)=0:

  1. 因为oldCap是2的n次幂的整数,其二进制表达为1个1后面跟n个0:1000…0,若想要e.hash&oldCap的结果为0,则e.hash的二进制形式中与对应oldCap的二进制的1的位置一定为0,其他位置的可以随意,这样即可保证结果为0;
  2. 假设:
    oldCap= 2 ^ 3 =8 = 1000
    则e.hash可以是 0101

e.hash&oldCap 0000=0

  1. (2oldCap -1)=2 ^ 4-1=01111,其二进制位数比oldCap多一位,但多的这一位是0,其余都是1(其低三位肯定也是1);(oldCap-1)=2 ^ 3-1=0111,其二进制位数与oldCap相同,且其低3位的值都是1。故(2oldCap-1)和(oldCap -1)两者与只有4位且首位为0的e.hash=0101计算时,其实只有低3位真正能影响计算结果,而两者的低3位相同,都是111;
  2. 故在前提条件下,(2oldCap-1)和(oldCap -1)两者与e.hash进行&运算之后的结果一样:
    (2oldCap -1)=2 ^ 4-1= 01111 (oldCap-1)=2 ^ 3-1= 0111
    e.hash 0101 e.hash 0101

e.hash&oldCap 00101=5 e.hash&oldCap 0101=5

  1. 而(oldCap -1) &e.hash恰巧代表的就是e元素在旧数组中的索引位置;
    而(2oldCap -1) &e.hash则代表的就是e元素在旧数组长度扩容2倍后的新数组里的索引位置
  2. 综上,可得出满足e.hash&oldCap=0的元素,其在新旧数组中的索引位置不变;

推导过程2(e.hash & oldCap)不等于0:

  1. 因为oldCap是2的n次幂的整数,其二进制表达为1个1后面跟n个0:1000…0,若想要e.hash&oldCap的结果不为0,则e.hash的二进制形式中与对应oldCap的二进制的1的位置一定不为0,其他位置的可以随意,这样即可保证结果不为0;
  2. 假设:
    oldCap= 2 ^ 3 =8 = 1000
    则e.hash可以是 1101

e.hash&oldCap 1000=13

  1. (2oldCap -1)=2 ^ 4-1=01111,其二进制位数比oldCap多一位,但多的这一位是0,其余都是1(其低三位肯定也是1,其从左到右数的第4位为1);(oldCap-1)=2 ^ 3-1=0111,其二进制位数与oldCap相同,且其低3位的值都是1, 其从左到右数的第4位为0,。故(2oldCap-1)和(oldCap -1)两者与只有4位且首位为1的e.hash=1101计算时,其实也只有从左到右数的第4位(0)真正能影响计算结果,,因为低3位完全一样都是1;
  2. 故在前提条件下,(2oldCap-1)和(oldCap -1)两者与e.hash进行&运算后结果相差了oldCap:
    (2oldCap -1)=2^4-1= 01111 ( oldCap - 1 ) =2 ^ 3-1= 0111
    e.hash 1101 e.hash 1101

(2oldCap -1)& e.hash 01101=8+5 (oldCap -1)&e.hash 0101=5

  1. 而(oldCap -1) &e.hash恰巧代表的就是e元素在旧数组中的索引位置;
    而(2oldCap -1) &e.hash则代表的就是e元素在旧数组长度扩容2倍后的新数组里的索引位置
  2. 综上,可得出满足e.hash&oldCap不等于0的元素,其在新数组中的索引位置是其在旧数组中索引位置的基础上再加上旧数组长度个偏移量。
目录
相关文章
|
23天前
|
机器学习/深度学习 算法 数据挖掘
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构。本文介绍了K-means算法的基本原理,包括初始化、数据点分配与簇中心更新等步骤,以及如何在Python中实现该算法,最后讨论了其优缺点及应用场景。
73 4
|
2月前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
54 3
|
21天前
|
存储 算法 安全
SnowflakeIdGenerator-雪花算法id生成方法
SnowflakeIdGenerator-雪花算法id生成方法
20 1
|
24天前
|
JSON 算法 数据挖掘
基于图论算法有向图PageRank与无向图Louvain算法构建指令的方式方法 用于支撑qwen agent中的统计相关组件
利用图序列进行数据解读,主要包括节点序列分析、边序列分析以及结合节点和边序列的综合分析。节点序列分析涉及节点度分析(如入度、出度、度中心性)、节点属性分析(如品牌、价格等属性的分布与聚类)、节点标签分析(如不同标签的分布及标签间的关联)。边序列分析则关注边的权重分析(如关联强度)、边的类型分析(如管理、协作等关系)及路径分析(如最短路径计算)。结合节点和边序列的分析,如子图挖掘和图的动态分析,可以帮助深入理解图的结构和功能。例如,通过子图挖掘可以发现具有特定结构的子图,而图的动态分析则能揭示图随时间的变化趋势。这些分析方法结合使用,能够从多个角度全面解读图谱数据,为决策提供有力支持。
|
2月前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
63 5
|
2月前
|
算法 索引
让星星⭐月亮告诉你,HashMap的resize()即扩容方法源码解读(已重新完善,如有不足之处,欢迎指正~)
`HashMap`的`resize()`方法主要用于数组扩容,包括初始化或加倍数组容量。该方法首先计算新的数组容量和扩容阈值,然后创建新数组。接着,旧数组中的数据根据`(e.hash & oldCap)`是否等于0被重新分配到新数组中,分为低位区和高位区两个链表,确保数据迁移时的正确性和高效性。
69 3
|
2月前
|
机器学习/深度学习 算法
让星星⭐月亮告诉你,HashMap之tableSizeFor(int cap)方法原理详解(分2的n次幂和非2的n次幂两种情况讨论)
`HashMap` 的 `tableSizeFor(int cap)` 方法用于计算一个大于或等于给定容量 `cap` 的最小的 2 的幂次方值。该方法通过一系列的无符号右移和按位或运算,逐步将二进制数的高位全部置为 1,最后加 1 得到所需的 2 的幂次方值。具体步骤包括: 1. 将 `cap` 减 1,确保已经是 2 的幂次方的值直接返回。 2. 通过多次无符号右移和按位或运算,将最高位 1 后面的所有位都置为 1。 3. 最终加 1,确保返回值为 2 的幂次方。 该方法保证了 `HashMap` 的数组容量始终是 2 的幂次方,从而优化了哈希表的性能。
34 1
|
2月前
|
Java
让星星⭐月亮告诉你,HashMap中保证红黑树根节点一定是对应链表头节点moveRootToFront()方法源码解读
当红黑树的根节点不是其对应链表的头节点时,通过调整指针的方式将其移动至链表头部。具体步骤包括:从链表中移除根节点,更新根节点及其前后节点的指针,确保根节点成为新的头节点,并保持链表结构的完整性。此过程在Java的`HashMap$TreeNode.moveRootToFront()`方法中实现,确保了高效的数据访问与管理。
30 2
|
2月前
|
Java 索引
让星星⭐月亮告诉你,HashMap之往红黑树添加元素-putTreeVal方法源码解读
本文详细解析了Java `HashMap` 中 `putTreeVal` 方法的源码,该方法用于在红黑树中添加元素。当数组索引位置已存在红黑树类型的元素时,会调用此方法。具体步骤包括:从根节点开始遍历红黑树,找到合适位置插入新元素,调整节点指针,保持红黑树平衡,并确保根节点是链表头节点。通过源码解析,帮助读者深入理解 `HashMap` 的内部实现机制。
39 2
|
2月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
60 0