HashMap 扩容机制(resize)
候选人小李在面试时被问到:"HashMap 什么时候会扩容?"
小李答:"元素数量超过阈值时扩容。"
面试官追问:"JDK7 和 JDK8 的扩容有什么区别?"
小李说:"JDK8 用了红黑树?"
面试官摇头:"我问的是扩容算法,不是数据结构。"
小张彻底答不上来了。
【面试官心理】 扩容机制是 HashMap 的核心难点之一。JDK7 的扩容算法有个经典的并发死循环问题,这是面试常考的深水区。能说清楚 JDK7 和 JDK8 扩容差异的候选人,说明对 HashMap 源码有过深入研究。
一、扩容触发条件 🔴
1.1 触发时机
HashMap 的扩容由两个参数控制:
扩容触发条件:
size(元素数量)超过threshold(扩容阈值)- 默认情况:16 × 0.75 = 12,即第 13 个元素插入时触发扩容
1.2 扩容流程图
1.3 扩容倍数
扩容后容量翻倍,但阈值也翻倍。所以扩容后需要再插入 容量 × 0.75 - 容量 × 0.75 = 容量 × 0.75 个元素才会再次扩容。
【学习小结】
- 扩容阈值 = 容量 × 负载因子(默认 16 × 0.75 = 12)
- 第 13 个元素插入时触发第一次扩容
- 扩容后容量翻倍:16 → 32 → 64 → 128
- 阈值也翻倍:12 → 24 → 48 → 96
二、JDK 7 扩容算法 🔴
2.1 JDK 7 的 resize 方法
2.2 JDK 7 的 transfer 方法
2.3 JDK 7 扩容的问题
头插法导致的链表反转:
并发场景下的死循环:
JDK 7 的 HashMap 扩容在多线程环境下可能导致环形链表,造成 CPU 100% 的死循环。这是经典的并发问题,面试必问!
2.4 ❌ 错误示范
候选人原话:"HashMap 线程不安全,所以不要在多线程下使用。"
问题诊断:
- 知道结论但不知道原因
- 不能解释为什么会出现死循环
- 不能说明 JDK 8 怎么解决的
面试官内心 OS:"这个候选人只知道不能并发用 HashMap,但没有理解底层的原理。"
【面试官心理】 JDK 7 扩容死循环是 HashMap 面试的经典深水区。能说清楚头插法、链表反转、死循环形成原因的候选人,说明真正理解了扩容的每个细节。
三、JDK 8 扩容算法 🔴
3.1 JDK 8 的 resize 方法
3.2 JDK 8 的关键优化
不需要重新计算 hash!
为什么这样可行?
为什么用尾插法?
JDK 8 的扩容优化核心:
- 不需要重新计算 hash,用
(hash & oldCap)判断位置 - 使用尾插法,链表顺序不变,不会死循环
- 扩容后链表分为两类:保持原位置、移动到新位置 :::
【学习小结】 JDK 7 vs JDK 8 扩容算法对比:
- JDK 7:重新计算 hash,头插法,链表反转,可能死循环
- JDK 8:不重新计算 hash,尾插法,链表顺序不变,无死循环
四、扩容性能分析 🟡
4.1 扩容的时间复杂度
4.2 扩容的空间开销
4.3 扩容的代价
五、生产避坑清单 🟡
5.1 ❌ 常见错误代码
5.2 生产事故案例
事故回顾:2024 年双十一,某订单服务的 HashMap 扩容导致 GC 停顿 2 秒。
5.3 预估容量的最佳实践
:::warning ⚠️ HashMap 的默认容量是 16,负载因子是 0.75。如果数据量超过 12,必须扩容。预设容量可以避免扩容带来的性能开销。
六、面试追问链 🟡
6.1 第一层追问
面试官:"HashMap 扩容阈值是多少?"
候选人:capacity × loadFactor,默认 12。
面试官追问:"为什么是 12 不是 16?"
正确回答:因为容量是 16,负载因子是 0.75,16 × 0.75 = 12。当插入第 13 个元素时触发扩容。
6.2 第二层追问
面试官:"JDK 7 和 JDK 8 的扩容有什么区别?"
候选人:...
正确回答:
- JDK 7:重新计算 hash,头插法,可能形成环形链表导致死循环
- JDK 8:不重新计算 hash,用
(hash & oldCap)判断位置,尾插法,不会死循环
6.3 第三层追问
面试官:"JDK 8 的 (hash & oldCap) == 0 是什么意思?"
候选人:...
正确回答:oldCap 是 2 的幂次,如 16 = 0b10000。(hash & oldCap) 只看 hash 的第 5 位(如果 oldCap = 16):
- 如果是 0:新位置 = 旧位置
- 如果是 1:新位置 = 旧位置 + oldCap
6.4 第四层追问
面试官:"为什么 HashMap 的容量必须是 2 的幂次?"
候选人:...
正确回答:因为 2 的幂次 - 1 的二进制全是 1,hash & (n-1) 等价于 hash % n,但 & 比 % 快很多。另外,扩容时 hash & oldCap 判断位置也需要容量是 2 的幂次。
七、红黑树的 split 🟡
7.1 红黑树节点的扩容处理
7.2 扩容后红黑树的退化
【学习小结】 HashMap 扩容核心要点:
- 触发条件:
size > capacity × 0.75 - JDK 7:重新 hash + 头插法 → 可能死循环
- JDK 8:不重新 hash + 尾插法 +
(hash & oldCap)判断位置 → 无死循环 - 生产中必须预设容量,避免频繁扩容