HashMap扩容时通过(e.hash & oldCap)==0判断元素迁移位置:为0则下标不变,为1则下标加oldCap;链表拆为lo/hi两链保持顺序,红黑树先拆链再视长度决定是否树化。

HashMap 扩容时的重新哈希(rehash)过程,本质是把旧数组里的元素“搬家”到新数组中。用内存图解释,关键不是画满整个堆内存,而是聚焦在 桶数组(table)结构变化 和 单个元素迁移路径 上——这样最直观、也最贴近源码逻辑。
扩容前:16 容量数组的内存布局
假设当前 HashMap 有 13 个元素,触发扩容(因阈值 = 16 × 0.75 = 12)。此时 table 是长度为 16 的 Node[] 数组:
- 每个下标(0~15)是一个“桶”,可能为空,也可能指向链表头节点或红黑树根节点;
- 元素通过
hash & (16 - 1)即hash & 0b1111计算下标,只看 hash 值低 4 位; - 例如:hash = 0b10101010101(十进制 1365),低 4 位是
0101→ 下标 = 5。
扩容后:32 容量数组的内存布局
新 table 长度为 32,所有元素需重新定位。但 JDK 8 不是逐个调用 hashCode() 再算 hash & 31,而是利用一个关键规律:
- 新容量 32 = 旧容量 16 × 2,二进制为
100000,32 - 1 = 31 = 0b11111; - 相比旧掩码
0b1111,新掩码多出一位——即第 5 位(从 0 开始计,也就是oldCap对应的 bit); - 因此,只需检查每个元素 hash 值的这一位:
→ 若为 0:新下标 = 旧下标;
→ 若为 1:新下标 = 旧下标 + 16。
这个判断直接用 (e.hash & oldCap) == 0 实现(oldCap = 16 = 0b10000),高效且无需重哈希。
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
迁移过程的内存图示意(以桶下标 5 为例)
旧数组下标 5 处有一条 3 节点链表:
NodeA → NodeB → NodeC
- 遍历该链表,对每个节点 e 计算
e.hash & 16: - 若 NodeA.hash = 0b10000101(低 5 位 00101),& 16 = 0 → 归入 低位链 lo,新下标仍为 5;
- 若 NodeB.hash = 0b10010101(低 5 位 10101),& 16 = 16 ≠ 0 → 归入 高位链 hi,新下标 = 5 + 16 = 21;
- NodeC 同理归类;
- 最后,lo 链整体挂到 newTable[5],hi 链整体挂到 newTable[21]。
红黑树和单节点的处理差异
迁移不是统一逻辑,取决于桶内结构:
- 单节点:直接按上述高位判断,放入 newTable[oldIndex] 或 newTable[oldIndex + oldCap];
- 链表:拆成两个子链(lo/hi),保持插入顺序(JDK 8 稳定性保障);
- 红黑树:先按高位拆成两个链表;若任一链表长度 ≤ 6,则转为链表存入对应桶;否则分别树化。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










