JDK 8 中 HashMap 扩容时,若桶中链表长度≥8但数组长度<64,则不树化而仅扩容;如初始容量16、负载因子0.75,达12元素触发扩容至32,此时链表长9仍不树化。

当 JDK 8 中 HashMap 扩容时,如果某个桶(bucket)中的链表长度大于 8,但**尚未满足树化条件**(即当前数组长度
扩容优先于树化
这是 JDK 8 的关键设计原则:在链表长度 ≥ 8 时,先检查数组长度是否 ≥ 64。不满足则跳过树化,仅执行 resize() —— 即将数组容量翻倍(如从 16 → 32 → 64),并重新哈希所有元素分布到新桶中。
- 目的:通过扩容稀释哈希冲突,降低单个桶的链表长度,往往比树化更轻量、更有效
- 实际效果:原长度为 9 的链表,在扩容后大概率被拆散到多个桶中,各链表变短,避免了红黑树开销
- 注意:树化阈值(TREEIFY_THRESHOLD = 8)只是触发检查的信号,不是强制转换指令
树化被跳过的典型场景
例如初始 HashMap 未指定容量,使用默认 16:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 插入第 13 个元素时,总 size > 16 × 0.75 = 12 → 触发首次扩容(16 → 32)
- 若某桶链表此时已达 9 个节点,因数组长度 16 treeifyBin() 内部仅调用 resize(),不会建树
- 后续继续插入,直到数组长度达到 64 且同一桶链表再次 ≥ 8,才会真正树化
扩容过程中链表如何迁移
JDK 8 扩容时对每个桶的链表做「分治式重散列」:
立即学习“Java免费学习笔记(深入)”;
- 旧数组长度为 n,新数组为 2n;桶索引由
(n - 1) & hash计算 - 扩容后,每个原链表节点根据 hash 的第 (log₂n) 位是 0 还是 1,被分配到「原索引位置」或「原索引 + n」位置
- 尾插法保证迁移后链表顺序不变,也避免了 JDK 7 的头插法导致的死循环问题
不树化不等于没优化
即使链表暂未树化,JDK 8 已通过以下方式缓解长链表影响:
- 链表插入采用尾插法,避免遍历时反复跳转
- 扩容阈值(负载因子 0.75)本身已预留缓冲空间,减少高冲突概率
- 哈希扰动(hash 方法中多次异或)提升低位参与度,使 hash 分布更均匀

















