链表转红黑树需同时满足两个条件:桶内链表长度≥8且数组容量≥64;若容量不足64则优先扩容而非树化;转换在putVal插入新节点后检查,由treeifyBin先判容量再执行树化。

Java HashMap 在链表过长时转红黑树,不是简单“一到8就转”,而是有明确的双重条件和具体执行流程。
触发转换的两个硬性条件
只有同时满足以下两点,链表才会被树化:
- 当前桶(bucket)中的链表长度 ≥ 8(即 TREEIFY_THRESHOLD = 8)
- 整个 HashMap 的底层数组长度(capacity)≥ 64(即 MIN_TREEIFY_CAPACITY = 64)
如果数组还不到 64,即使某条链表已有 10 个节点,也不会转树——而是先触发扩容(resize()),因为小容量下长链表更可能是整体散列不均,而非局部冲突严重。
转换发生在插入过程的哪个环节?
转换不是在插入后单独调度的,而是在 putVal() 方法末尾、完成新节点插入之后立即检查并执行:
立即学习“Java免费学习笔记(深入)”;
- 遍历完链表,确认 key 不重复,把新节点插入链表尾部
- 此时统计该桶中节点总数(含新节点)
- 若总数 ≥ 8,且数组长度 ≥ 64,则调用
treeifyBin(tab, hash)
注意:treeifyBin 并不直接建树,它先判断容量是否够;不够就扩容,够了才真正调用 hd.treeify(tab) 开始链表→红黑树节点→自平衡建树。
链表怎么变成红黑树?关键三步
实际转换不是“重写结构”,而是对原有链表节点进行就地升级:
-
节点类型转换:把每个
Node包装成TreeNode(继承自 Node,新增红黑树所需字段如parent、left、right、red) -
构建树形链接:按原有顺序逐个插入,但插入逻辑走红黑树的二叉搜索路径(基于 key 的
compareTo或hashCode比较) - 自动平衡:每次插入都按红黑树规则调整颜色与旋转,最终得到一棵满足五条性质的近似平衡树
整个过程不改变 key-value 数据,只改变存储结构和查找方式。
为什么是 8 和 6,而不是其他数字?
这不是拍脑袋定的,而是基于泊松分布的数学推演:
- 在负载因子 0.75 下,理想哈希分布中,桶内元素数量服从 λ=0.5 的泊松分布
- 桶中恰好有 8 个元素的概率约为 10⁻⁶,极低——说明一旦出现,大概率是哈希函数缺陷或数据异常,值得用更稳定结构应对
- 设退化阈值为 6(而非 7 或 8),是为了留出缓冲区间,避免刚树化又删一个就退化、再插一个又树化,反复横跳浪费性能


















