
HashMap中出现哈希冲突的时候,链表长度>=8且数组长度>=64时,才会启用红黑树,而当红黑树节点减少到6个以下时,则会重新退化成链表。那哈希值相同时,红黑树的比较规则(源码实际逻辑)如下:
当两个 key 的 hashCode() 相等时(例如 "Aa" 和 "BB" 的哈希值可能相同),HashMap 会按以下顺序决定大小:
如果 key 实现了
Comparable接口(比如 String、Integer 等):
调用key1.compareTo(key2)。返回负数 → key1 放在左边
返回正数 → key1 放在右边
返回 0 → 说明两个 key 真正相等(
equals也应为 true),此时不会插入新节点,而是覆盖旧值。
如果 key 没有实现
Comparable(比如自定义的普通类):HashMap会调用System.identityHashCode(key)获得一个基于对象内存地址的原始哈希值(不会重写,所以基本唯一)。
然后用这个值来比较,保证总能分出左右。
实际源码中还有一个
tieBreakOrder方法,当上述方法仍然无法区分时(极少情况),还会进一步用类名等做最终比较。
结论:哈希值相同并不会导致红黑树混乱,因为还有第二、第三层比较规则,直到能决定左右顺序为止。
分出了红黑树的左右之后,我们来思考下一组数据到底是如何存入到红黑树中的?以下数据类似于哈希值。
下面我把这组数据:
10, 20, 30, 15, 25, 5
一步一步插入红黑树,详细说明:
插入位置怎么找
节点颜色怎么染
为什么变色
为什么旋转
每一步树长什么样
一、红黑树插入规则
插入时固定流程:
1. 先按二叉搜索树插入:小的放左边,大的放右边
2. 插入的新节点默认红色
3. 若破坏规则,再调整:叔叔红色则变色,叔叔为空则旋转
核心规则:
根节点必须黑
红节点不能连红
黑高度一致:每条路径上黑色节点的个数一致,红黑树中,黑色节点为真正骨架,红色节点为临时节点。
注意:红黑树规定所有null都看作黑色
二、插入 10
插入位置
树为空,所以:
10 成为根节点
新节点默认红色:
10(R)
但根必须黑:
所以改成:
10(B)
最终:
10(B)
三、插入 20
查找位置
比较:
20 > 10
放右边
新节点默认红:
10(B)
\
20(R)
检查:
父节点 10 是黑色:
合法,无需调整
四、插入 30
查找位置
比较:
30 > 10
30 > 20
放到20右边
插入后:
10(B)
\
20(R)
\
30(R)
问题:
20(R)
30(R)
连续红色违规
五、判断关系
当前:
30 = 当前节点
20 = 父节点
10 = 祖父节点
叔叔节点 = null(黑)
结构:
右 → 右
属于:
RR 型
六、RR型处理:左旋 + 重新染色
对 10 左旋:
旋转前:
10
\
20
\
30
旋转后:
20
/ \
10 30
重新染色:
20 -> 黑
10 -> 红
30 -> 红
结果:
20(B)
/ \
10(R) 30(R)
七、插入 15
查找位置
比较:
15 < 20
15 > 10
放10右边
插入后:
20(B)
/ \
10(R) 30(R)
\
15(R)
问题:
10 红
15 红
违规
八、找叔叔
当前:
15 = 当前节点
10 = 父节点
20 = 祖父节点
30 = 叔叔节点
叔叔:
30(R)
叔叔是红色
九、叔叔红 → 只变色
规则:
父变黑
叔变黑
祖父变红
变色:
20(R)
10(B)
30(B)
变成:
20(R)
/ \
10(B) 30(B)
\
15(R)
根必须黑:
20 -> 黑
最终:
20(B)
/ \
10(B) 30(B)
\
15(R)
十、插入 25
查找位置
比较:
25 > 20
25 < 30
放30左边
插入:
20(B)
/ \
10(B) 30(B)
\ /
15(R) 25(R)
检查:
父节点:
30(B)
父是黑色:
无需调整
十一、插入 5
查找位置
比较:
5 < 20
5 < 10
放10左边
插入后:
20(B)
/ \
10(B) 30(B)
/ \ /
5(R) 15(R) 25(R)
父节点:
10(B)
父黑:
合法
十二、最终红黑树
20(B)
/ \
10(B) 30(B)
/ \ /
5(R) 15(R) 25(R)
十三、每一步颜色变化总结
插入10
10(R) → 10(B)
插入20
20 = 红
插入30
旋转前:
10(B)
\
20(R)
\
30(R)
旋转后:
20(B)
10(R)
30(R)
插入15
叔叔红:
10(R) → 黑
30(R) → 黑
20(B) → 红 → 再变黑
插入25
25 = 红
插入5
5 = 红
十四、为什么这样设计?
设计思想:
红色节点
表示:
临时扩展
黑色节点
表示:
稳定主干
所以树会自动形成:
黑色骨架
+ 红色叶子
像这样:
主结构稳定
局部灵活
十五、查找15的路径
查找:
15
路径:
20 → 10 → 15
比较:
15 < 20
15 > 10
15 = 15
找到。
十六、时间复杂度
树高度保持:
[
O(\log n)
]
所以查找:
[
O(\log n)
]
十七、一句话总结
这组数据插入过程核心是:
新节点先红
红红冲突:
叔叔红 → 变色
叔叔黑 → 旋转
最后根变黑