TreeMap 的底层原理详解
一、数据结构
TreeMap 是基于 红黑树(Red-Black Tree) 实现的有序映射(SortedMap),其核心特性为 按键的自然顺序或自定义比较器排序。以下是其核心结构:
-
红黑树特性
• 平衡规则:
◦ 每个节点为红色或黑色。
◦ 根节点和叶子节点(NIL)必须为黑色。
◦ 红色节点的子节点必须为黑色(即不能有连续红色节点)。
◦ 从任一节点到其所有叶子节点的路径包含相同数量的黑色节点(黑高相同)。
• 平衡效果:红黑树通过上述规则确保树的高度近似为O(log n),从而保证操作的高效性。 -
节点结构(Entry)
static final class Entry<K,V> implements Map.Entry<K,V> { K key; V value; Entry<K,V> left; // 左子节点 Entry<K,V> right; // 右子节点 Entry<K,V> parent; // 父节点 boolean color = BLACK; // 节点颜色(默认黑色) }
二、核心操作原理
1. 插入(put)
• 步骤:
a. 二叉查找树插入:根据键的顺序找到插入位置,创建新节点(初始颜色为红色)。
b. 平衡修复(fixAfterInsertion):通过旋转和颜色调整修复红黑树性质。
• 修复逻辑:
• 情况 1:插入节点的父节点是黑色。
◦ 无需调整,直接插入。
• 情况 2:插入节点的父节点是红色。
◦ Case 2a:叔节点(父节点的兄弟节点)为红色。
将父节点和叔节点变为黑色,祖父节点变为红色,递归处理祖父节点。
◦ Case 2b:叔节点为黑色或不存在,且插入节点与父节点的方向不一致(如父节点是左子,插入节点是右子)。
对父节点左旋或右旋,转化为 Case 2c。
◦ Case 2c:叔节点为黑色或不存在,且插入节点与父节点的方向一致。
将父节点变为黑色,祖父节点变为红色,对祖父节点进行旋转。
2. 删除(remove)
• 步骤:
a. 二叉查找树删除:找到待删除节点,用其后继节点(右子树的最小节点)替代。
b. 平衡修复(fixAfterDeletion):根据替代节点的颜色调整树结构。
• 修复逻辑:
• 若替代节点为红色,直接删除,无需调整。
• 若替代节点为黑色,需从删除位置向上递归修复黑高平衡。
◦ Case 1:兄弟节点为红色。
将兄弟节点变为黑色,父节点变为红色,对父节点旋转,转化为其他情况。
◦ Case 2:兄弟节点为黑色,且其子节点均为黑色。
将兄弟节点变为红色,递归处理父节点。
◦ Case 3:兄弟节点为黑色,且远侄子节点(兄弟节点的右子节点)为红色。
交换父节点与兄弟节点颜色,将远侄子节点变为黑色,对父节点旋转。
◦ Case 4:兄弟节点为黑色,且近侄子节点(兄弟节点的左子节点)为红色。
将兄弟节点变为红色,近侄子节点变为黑色,对兄弟节点旋转,转化为 Case 3。
3. 查询(get)
• 原理:基于二叉查找树的搜索逻辑,时间复杂度为 O(log n)。
final Entry<K,V> getEntry(Object key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = comparator.compare(key, p.key);
if (cmp < 0) p = p.left;
else if (cmp > 0) p = p.right;
else return p;
}
return null;
}
三、有序性实现
-
中序遍历
TreeMap 的键值对按 左子树 → 根节点 → 右子树 的顺序遍历,保证升序排列。
• EntrySet 迭代器:Iterator<Map.Entry<K,V>> it = treeMap.entrySet().iterator(); while (it.hasNext()) { Map.Entry<K,V> entry = it.next(); // 按顺序访问键值对 } -
范围查询
支持subMap()、headMap()、tailMap()等方法,利用红黑树的有序特性快速定位范围。
四、性能分析
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 插入(put) | O(log n) | 需平衡修复,但红黑树高度为对数级 |
| 删除(remove) | O(log n) | 同插入,需处理最坏情况的平衡调整 |
| 查询(get) | O(log n) | 类似二分查找 |
| 遍历(forEach) | O(n) | 中序遍历所有节点 |
五、与 HashMap 的对比
| 特性 | TreeMap | HashMap |
|---|---|---|
| 数据结构 | 红黑树 | 数组 + 链表/红黑树 |
| 顺序性 | 按键有序 | 无序 |
| 时间复杂度(增删查) | O(log n) | O(1)(理想情况下) |
| 内存占用 | 较高(存储父节点和颜色) | 较低 |
| 适用场景 | 需排序或范围查询 | 快速随机访问 |
六、线程安全与扩展
-
线程不安全
TreeMap 未内置同步机制,多线程环境下需通过外部同步或使用Collections.synchronizedSortedMap()包装。SortedMap<K,V> syncTreeMap = Collections.synchronizedSortedMap(new TreeMap<>()); -
并发替代方案
在并发场景下,可选用ConcurrentSkipListMap,基于跳表(Skip List)实现,提供类似的有序性和线程安全。
七、典型应用场景
- 需要按键排序的映射,如字典序、数值排序等。
- 范围查询操作,例如统计某个区间内的键值对。
- 实现有序缓存,结合 LRU 策略的变种(需自定义比较器)。
总结
TreeMap 的核心原理基于 红黑树,通过颜色和旋转规则维持树的平衡,保证操作的高效性(O(log n))。其优势在于:
• 有序性:支持自然排序或自定义排序。
• 范围操作:高效实现子映射查询。
• 稳定性:在动态数据场景下性能优于普通二叉查找树。
1627

被折叠的 条评论
为什么被折叠?



