HashMap扩容时通过hash & oldCap判断高位是否为1:结果为0则新索引=原索引,非0则新索引=原索引+旧容量;复用已存hash值,无需重新扰动,仅两次位与运算即完成索引重定位。

HashMap 的 resize 过程中,重新计算索引不是靠重新调用 hash() % newCapacity,而是利用数组长度为 2 的幂这一特性,通过位运算高效完成。核心是:每个节点在新数组中的位置,要么落在原索引(旧桶),要么落在 原索引 + 旧容量 的位置。
为什么能只看高位就确定新位置
因为新容量 = 旧容量 × 2,所以新数组长度的二进制比旧长度多一个最高位(例如 16 → 32,即 10000 → 100000)。这意味着新数组的掩码(newCapacity - 1)比旧掩码多一位有效位。而这一位是否为 1,恰好由原 hash 值的对应高位决定。
具体来说,JDK 8 中判断方式是:取 hash 值与旧容量做按位与(hash & oldCap)。结果为 0 表示高位为 0,新索引 = 原索引;结果非 0 表示高位为 1,新索引 = 原索引 + 旧容量。
迁移时如何分组处理节点
扩容时遍历旧数组每个桶,对桶内链表或红黑树节点逐个处理:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
立即学习“Java免费学习笔记(深入)”;
- 对每个节点计算
hash & oldCap - 结果为 0 的节点归入“低位链表/树”,保留在原索引位置
- 结果非 0 的节点归入“高位链表/树”,放入
原索引 + oldCap位置 - 链表使用尾插法拼接,避免 JDK 7 头插法导致的循环链表问题
- 红黑树节点同样按高位分两组,分别构建新树或退化为链表(若节点数 ≤ 6)
不需要重新执行 hash() 方法
二次哈希(hash())只在 put 时计算一次,存入 Node 的 hash 字段。resize 时直接复用该值,不重复扰动。这省去了大量重复计算,是 JDK 8 相比 JDK 7 的关键优化之一。
举个实际例子
假设旧容量为 16(oldCap = 16,二进制 10000),某节点 hash 值为 0x10011010(十进制 154):
- 原索引 =
hash & (16 - 1)=0x10011010 & 0x00001111=0x00001010= 10 - 高位判断 =
hash & oldCap=0x10011010 & 0x00010000=0x00010000≠ 0 → 属于高位组 - 新容量为 32,新索引 =
10 + 16= 26
整个过程没有模运算,只有两次位与,极快。

















