TreeMap 是 Java 集合框架中基于红黑树(Red-Black Tree)实现的有序映射(NavigableMap
),它支持键的自然排序或自定义排序,且所有核心操作(put
、get
、remove
)的时间复杂度为 O(log n)
。本文将从数据结构、核心方法、设计模式应用等角度,深入解析 TreeMap 的源码实现。
一、核心数据结构:红黑树
TreeMap 的核心是一棵红黑树,这是一种自平衡的二叉搜索树(BST)。红黑树通过颜色标记(红/黑)和 5 条约束保证树的平衡,避免退化为链表(时间复杂度退化为 O(n)
)。红黑树的约束如下:
- 每个节点要么是红色,要么是黑色。
- 根节点是黑色。
- 所有叶子节点(NIL)是黑色。
- 红色节点的两个子节点都是黑色(不存在连续红节点)。
- 从任一节点到其每个叶子的所有路径包含相同数量的黑色节点(黑高平衡)。
TreeMap 的内部节点由 Entry 类表示,包含键(key
)、值(value
)、左右子节点(left
/right
)、父节点(parent
)和颜色标记(color
)。
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;
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
}
二、构造函数与排序策略
TreeMap 支持两种排序方式,通过构造函数初始化排序规则:
- 自然排序:无参构造函数(
TreeMap()
),要求键实现Comparable
接口。 - 自定义排序:带
Comparator
的构造函数(TreeMap(Comparator<? super K> comparator)
),使用指定比较器排序。
// 自然排序构造函数(源码第 118 行)
public TreeMap() {
comparator = null;
}
// 自定义排序构造函数(源码第 126 行)
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
构造函数的核心是初始化 comparator
字段。后续插入、查找操作会根据 comparator
是否为 null
,选择使用 Comparable.compareTo
或 Comparator.compare
进行键的比较。
三、核心方法解析
1. put(K key, V value)
:插入与平衡
put
方法的核心逻辑:在红黑树中找到插入位置,插入新节点后通过旋转和颜色调整修复红黑树约束。
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
步骤分解:
- 空树处理:若树为空(
root == null
),直接创建根节点(颜色默认为黑色,因红黑树约束 2)。 - 查找插入位置:根据
comparator
是否存在,选择比较方式(Comparator.compare
或Comparable.compareTo
),从根节点开始向下查找,直到找到合适的叶子节点位置。 - 插入新节点:新节点颜色初始化为红色(避免破坏黑高约束),并链接到父节点的左或右子节点。
- 修复红黑树:调用
fixAfterInsertion(Entry<K,V> x
) 修复可能违反的红黑树约束(如连续红节点)。
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
fixAfterInsertion
修复逻辑: 插入新节点(红色)可能导致父节点为红色(违反约束 4)。修复分两种情况(以父节点是祖父节点的左子节点为例):
- 叔叔节点为红色:将父节点和叔叔节点设为黑色,祖父节点设为红色,然后向上递归修复祖父节点(可能引发更高层的冲突)。
- 叔叔节点为黑色:若新节点是父节点的右子节点,先左旋父节点,转换为左子节点情况;然后将父节点设为黑色,祖父节点设为红色,右旋祖父节点。
通过上述操作,最终保证根节点为黑色(约束 2),并恢复红黑树的平衡。
2. get(Object key)
:查找节点
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
get
方法通过 getEntry(key)
实现(源码第 373-400 行),核心是二叉搜索树的查找逻辑:
- 若
comparator
存在,使用getEntryUsingComparator
按比较器查找。 - 否则,要求键实现
Comparable
,使用compareTo
方法比较。 - 从根节点开始,根据比较结果向左或右子树递归查找,直到找到相等的键或遍历至叶子节点(返回
null
)。
3. remove(Object key)
:删除与平衡
public V remove(Object key) {
Entry<K,V> p = getEntry(key);
if (p == null)
return null;
V oldValue = p.value;
deleteEntry(p);
return oldValue;
}
remove
方法首先调用 getEntry
找到待删除节点,然后调用 deleteEntry
执行删除并修复红黑树。
private void deleteEntry(Entry<K,V> p) {
modCount++;
size--;
// If strictly internal, copy successor's element to p and then
// point to successor.
if (p.left != null && p.right != null) {
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
// Link replacement to parent
replacement.parent = p.parent;
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
// Null out links so they are OK to use by fixAfterDeletion
p.left = p.right = p.parent = null;
// Fix replacement
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only nod
root = null;
} else { // No children. Use self as phantom replacement and u
if (p.color == BLACK)
fixAfterDeletion(p);
if (p.parent != null) {
if (p == p.parent.left)
p.parent.left = null;
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}
删除逻辑:
- 节点有两个子节点:找到后继节点(右子树的最小节点),将后继节点的键值复制到当前节点,转换为删除后继节点(最多一个子节点)。
- 节点有一个子节点或无子节点:用子节点(或
null
)替换当前节点的位置,若删除的是黑色节点,调用fixAfterDeletion
修复红黑树。
private void fixAfterDeletion(Entry<K,V> x) {
while (x != root && colorOf(x) == BLACK) {
if (x == leftOf(parentOf(x))) {
Entry<K,V> sib = rightOf(parentOf(x));
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateLeft(parentOf(x));
sib = rightOf(parentOf(x));
}
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(rightOf(sib)) == BLACK) {
setColor(leftOf(sib), BLACK);
setColor(sib, RED);
rotateRight(sib);
sib = rightOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(rightOf(sib), BLACK);
rotateLeft(parentOf(x));
x = root;
}
} else { // symmetric
Entry<K,V> sib = leftOf(parentOf(x));
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateRight(parentOf(x));
sib = leftOf(parentOf(x));
}
if (colorOf(rightOf(sib)) == BLACK &&
colorOf(leftOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(leftOf(sib)) == BLACK) {
setColor(rightOf(sib), BLACK);
setColor(sib, RED);
rotateLeft(sib);
sib = leftOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(leftOf(sib), BLACK);
rotateRight(parentOf(x));
x = root;
}
}
}
setColor(x, BLACK);
}
fixAfterDeletion
修复逻辑: 删除黑色节点会导致其所在路径的黑高减少(违反约束 5)。修复通过调整兄弟节点的颜色和旋转,确保路径黑高一致。例如,若删除节点是左子节点:
- 若兄弟节点为红色,左旋父节点并调整颜色,转换为兄弟节点为黑色的情况。
- 若兄弟节点的两个子节点均为黑色,将兄弟节点设为红色,向上递归修复父节点。
- 否则,通过旋转和颜色调整,将额外的黑色转移到树的高层。
四、导航功能与子映射
TreeMap
实现了 NavigableMap
接口,提供丰富的导航方法(如 lowerEntry
、ceilingKey
等),用于查找接近指定键的节点。这些方法通过 getLowerEntry
、getCeilingEntry
等内部方法实现,核心是二叉搜索树的边界查找。
final Entry<K,V> getCeilingEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
if (cmp < 0) {
if (p.left != null)
p = p.left;
else
return p;
} else if (cmp > 0) {
if (p.right != null) {
p = p.right;
} else {
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.right) {
ch = parent;
parent = parent.parent;
}
return parent;
}
} else
return p;
}
return null;
}
例如,getCeilingEntry(K key)
(源码第 433-458 行)查找大于等于 key 的最小键节点:
- 从根节点开始比较,若
key
小于当前节点,向左子树查找;若大于,向右子树查找。 - 若右子树为空,向上回溯父节点,直到找到第一个祖先节点大于
key
。
五、设计模式应用
1. 迭代器模式(Iterator Pattern)
TreeMap 的内部迭代器(如 EntryIterator
、KeyIterator
)通过 PrivateEntryIterator
基类实现,统一了遍历逻辑。子类只需重写 next()
方法定义具体返回值(键、值或键值对),符合迭代器模式“提供统一遍历接口”的思想。
// 迭代器基类(源码第 1533 行)
abstract class PrivateEntryIterator<T> implements Iterator<T> {
Entry<K,V> next;
Entry<K,V> lastReturned;
int expectedModCount;
public final boolean hasNext() { return next != null; }
final Entry<K,V> nextEntry() { /* 遍历逻辑 */ }
}
// 键迭代器(源码第 1589 行)
final class KeyIterator extends PrivateEntryIterator<K> {
public K next() { return nextEntry().key; }
}
2. 模板方法模式(Template Method Pattern)
NavigableSubMap
抽象类定义了子映射的通用操作(如 absLowest
、absHighest
),具体实现由子类 AscendingSubMap
和 DescendingSubMap
完成。例如,subLowest()
在升序子映射中返回最小节点,在降序子映射中返回最大节点,体现了模板方法模式“定义骨架,子类实现细节”的特点。
3. 工厂方法模式(Factory Method Pattern)
subMap
、headMap
、tailMap
等方法根据排序方向(升序/降序)返回 AscendingSubMap
或 DescendingSubMap
实例,隐藏了具体子类的创建逻辑,符合工厂方法模式“由子类决定实例化哪个类”的特征。
六、线程安全性与 fail-fast 机制
TreeMap 是非线程安全的。为保证多线程下的正确性,需通过 Collections.synchronizedSortedMap
包装。其迭代器(如 EntryIterator
)实现了 fail-fast
机制:通过 modCount
字段记录结构修改次数,若迭代过程中 modCount
变化,抛出 ConcurrentModificationException
,快速检测并发修改错误。
七、总结与适用场景
TreeMap 是基于红黑树的有序映射,核心优势是有序性和对数时间复杂度操作,适用于需要按键排序的场景(如统计、排序查询)。但需注意:
- 插入/删除操作因涉及红黑树修复,性能略低于 HashMap(但仍为
O(log n)
)。 - 非线程安全,多线程环境需额外同步。
- 键需支持比较(自然排序或自定义
Comparator
)。
通过深入理解 TreeMap 的源码,我们不仅掌握了红黑树的实际应用,还学习了设计模式在集合框架中的巧妙运用,为优化代码设计和解决复杂问题提供了思路。