treenode既是红黑树节点又是链表节点,因其继承linkedhashmap.entry(含before/after)且源自node(含prev/next),分别支持树结构(left/right/parent/red)和哈希桶内链表连接(prev/next)。

TreeNode为什么既是红黑树节点又是链表节点
因为 TreeNode 继承自 LinkedHashMap.Entry,而后者本身就有 before 和 after 字段——这是双向链表的前驱/后继指针。所以它天然支持两种结构:作为红黑树节点时用 left/right/parent/red;作为插入顺序链表节点时用 prev/next(注意:不是 before/after,prev/next 来自 Node 的继承,用于哈希桶内链表连接)。
常见错误现象:NullPointerException 出现在遍历 TreeNode 的 prev 或 next 时,其实是误把树节点当普通链表节点直接遍历,忽略了它只在「树化后仍需维持原桶内顺序」时才有效连接。
-
prev和next仅在该TreeNode被挂载到哈希桶数组中时被维护(比如调用moveRootToFront后),不是树结构的一部分 - 树内遍历必须走
left/right,不能靠next遍历整棵树 - 反树化(
untreeify)时会用prev/next快速重建链表,但此时left/right已失效
treeify() 怎么把链表转成红黑树
核心是两步:先构造一棵普通的二叉搜索树(BST),再通过 balanceInsertion 逐个染色+旋转,恢复红黑性质。整个过程不改变原有 Node 的 hash/key/value,只是把它们包装成 TreeNode 并重连指针。
使用场景:当某个桶中 Node 链表长度 ≥ 8 且哈希表容量 ≥ 64 时触发,否则只扩容不树化。
- 树化前会尝试用
comparableClassFor+compareComparables获取 key 的自然序;失败则 fallback 到tieBreakOrder(基于System.identityHashCode和类名哈希)保证可比性 - 所有新创建的
TreeNode默认为红色,插入后统一由balanceInsertion调整 - 注意:树化不是“复制数据”,而是原地升级节点类型——原来的
Node对象被强转为TreeNode,其next字段仍保留,用于维持桶内链表结构
removeTreeNode() 删除时最常踩的三个坑
删除操作远比插入复杂,因为要同时维护红黑树平衡和哈希桶链表结构,稍有不慎就断链或失衡。
常见错误现象:删除后 get() 返回 null、size() 不对、甚至 ConcurrentModificationException(多线程下未同步)。
- 没调用
unmap或漏掉root重连逻辑,导致该桶的tab[index]指向一个已从树中摘除但未从桶链表中移出的节点 - 删除后未调用
balanceDeletion,红黑树黑高失衡,后续查找可能漏节点(尤其在左/右子树黑高不等时) - 反树化阈值判断写错:JDK 规定树中节点 ≤ 6 才
untreeify,但有人误用“桶中总节点数 ≤ 6”,忽略当前已是树结构的事实
TreeNode 的 color 字段为什么是布尔型而不是枚举
因为 red 字段定义为 boolean red,不是 Color.RED/BLACK。这是纯粹的性能取舍:避免对象创建和虚方法分派,用单 bit 存储更省内存、更快访问。
性能影响:在高频插入/删除路径(如 putVal、removeNode)中,每次颜色判断都是 if (p.red) 这种直接位读取,比 if (p.color == Color.RED) 少一次字段解引用和常量比较。
- 所有红黑树操作(
rotateLeft、rotateRight、balanceInsertion)都基于这个布尔值做分支,没有抽象层 - 别试图给
TreeNode加 color getter/setter——它压根没提供,直接读写red字段是唯一正解 - 调试时打印
TreeNode看不到 color?那是toString()没输出它,得手动打p.red ? "RED" : "BLACK"
真正麻烦的从来不是“怎么写 TreeNode”,而是搞清它在哪一刻属于树、在哪一刻属于链表、又在哪一刻两者都得保——这三个身份切换全靠几行指针赋值,手抖一下,prev 指向了 right,或者 root 没挪到桶头,问题就藏进去了。









