HashMap链表转红黑树需同时满足数组长度≥64且链表节点数≥8;退化仅发生在resize后子树节点数≤6时;阈值8基于泊松分布λ=0.5的概率安全上限。

HashMap 什么时候会把链表转成红黑树
不是链表一长就转,得同时满足两个硬条件:table 数组长度 ≥ MIN_TREEIFY_CAPACITY(默认 64),且对应桶里链表节点数 ≥ TREEIFY_THRESHOLD(默认 8)。前者常被忽略——如果 HashMap 还没扩容到 64,哪怕某条链有 20 个节点,也死活不会树化。
- 扩容前链表很长?先检查
size和当前table.length,用map.size()和map.entrySet().size()都不能代替对底层数组状态的判断 - 手动触发树化?可以调用
map.putAll(new HashMap(...))强制扩容,但别为了树化而树化——红黑树节点内存开销比 Node 大近一倍 - Java 8 中
TreeNode继承自Node,但多了parent/left/right字段,GC 压力和缓存行利用率都受影响
为什么是泊松分布,而不是简单设个固定阈值
泊松分布用来建模「哈希均匀前提下,单个桶中元素数量的概率分布」。当负载因子 α = 0.75、数组长度为 n 时,理论期望桶长度是 α,但实际会有波动;泊松拟合发现:λ = 0.5 时,P(X ≥ 8) ≈ 10⁻⁷——意味着平均每 1000 万个桶才可能出现一次长度 ≥ 8 的极端情况,此时链表查找退化(O(n))已不可接受,必须树化。
- 这个 λ=0.5 不是拍脑袋定的,它来自实验观测:在大量随机 key 下,
table扩容后各桶元素数最贴近泊松(0.5) - 所以
TREEIFY_THRESHOLD = 8是概率意义上的“安全上限”,不是性能拐点——实测中链表查 8 个元素和查 7 个,耗时差异微乎其微 - 如果你的 key 哈希全落在几个桶里(比如只重写了
equals没重写hashCode),泊松模型失效,再高的阈值也没用
红黑树什么时候会退化回链表
只有一种情况:调用 resize() 扩容后,原红黑树所在桶被拆分,若拆分后任一子树节点数 ≤ UNTREEIFY_THRESHOLD(默认 6),就整个退化为链表——注意,是「拆分后子树节点数」,不是「原树总节点数」。
- 常见误解:以为删掉两个元素就会退化。错。删除不触发退化,只有 resize 才可能
- 如果扩容后某子树刚好剩 6 个节点,它变成链表;但如果剩 7 个,它还是红黑树——这个 6 是硬编码在
untreeify()里的,不可配置 - 退化过程是整体重建:遍历整棵树,按新 hash 计算位置,重新连成链表,不是“剪掉两个节点”那么简单
冲突多但不想树化?绕过方案和代价
真遇到高频哈希冲突(比如用时间戳做 key),与其等树化,不如从源头控制。JDK 自己都不建议靠树化兜底。
立即学习“Java免费学习笔记(深入)”;
- 重写
hashCode():确保不同对象尽量返回不同值,哪怕多算几轮位运算,也比依赖Object.hashCode()强 - 初始化时预估容量:
new HashMap(expectedSize / 0.75f + 1),减少扩容次数,也就减少了触发树化的频次 - 换数据结构:冲突持续高,
ConcurrentHashMap的分段锁+树化策略更稳;或者直接上TreeMap(但失去 O(1) 查找) - 用
LinkedHashMap无法避免冲突,但它能保证迭代顺序,排查时容易看出是不是哈希分布异常
红黑树退化逻辑藏在 split() 和 untreeify() 里,不看源码很难意识到:退化不是“性能差就倒退”,而是“结构拆散后太小,留着树反而浪费”。这点很多人调试半天才发现,桶里明明还有 7 个元素,怎么突然变链表了。










