一、TreeMap 概述
TreeMap 的定义
TreeMap 是 Java 集合框架中基于红黑树(Red-Black Tree)实现的有序键值对映射(Map)。它继承自 AbstractMap,实现了 NavigableMap 接口,默认按照键的自然顺序(Comparable 接口)或通过自定义比较器(Comparator)排序。
TreeMap 的核心特点
-
有序性
- 键值对按照键的顺序存储(升序或降序),迭代时直接输出排序后的结果。
- 支持按范围查询(如
subMap()、headMap()、tailMap())。
-
基于红黑树
- 红黑树是自平衡二叉查找树,保证插入、删除、查找的时间复杂度为 O(log n)。
- 相比
HashMap(O(1)),TreeMap牺牲部分性能换取有序性。
-
键的唯一性与排序约束
- 键不允许为
null(若使用自然排序,值可为null)。 - 键必须实现
Comparable接口,或通过构造器传入Comparator。
- 键不允许为
-
线程不安全
- 需手动同步(如
Collections.synchronizedSortedMap)。
- 需手动同步(如
示例代码(自然排序)
TreeMap<Integer, String> map = new TreeMap<>();
map.put(3, "Apple");
map.put(1, "Banana");
map.put(2, "Cherry");
System.out.println(map); // 输出:{1=Banana, 2=Cherry, 3=Apple}(自动按键升序排列)
适用场景
- 需要键值对按顺序遍历或范围查询时(如按分数段查询学生成绩)。
- 键的排序逻辑明确且稳定(如日期、数值等)。
注意事项
- 性能权衡:频繁插入/删除时,红黑树的平衡操作可能比
HashMap更耗时。 - 排序一致性:若键为自定义对象,需确保
compareTo()或Comparator逻辑与equals()一致(避免违反Set契约)。
TreeMap 的继承结构
1. 继承关系图
java.lang.Object
↳ java.util.AbstractMap<K,V>
↳ java.util.TreeMap<K,V>
2. 实现接口
java.util.NavigableMap<K,V>
(提供导航方法如lowerEntry(),floorKey()等)java.util.SortedMap<K,V>
(保证键的有序性)java.lang.Cloneable
(支持clone()方法)java.io.Serializable
(支持序列化)
3. 关键特点
- 基于红黑树(自平衡二叉查找树)实现
- 默认按键的自然顺序排序(依赖
Comparable接口) - 可通过
Comparator自定义排序规则 - 非线程安全(需手动同步)
4. 与 HashMap 对比
| 特性 | TreeMap | HashMap |
|---|---|---|
| 数据结构 | 红黑树 | 数组+链表/红黑树 |
| 排序 | 按键有序 | 无序 |
| 时间复杂度 | 增删查 O(log n) | 平均 O(1) |
| 允许 null 键 | 取决于 Comparator | 允许 |
TreeMap 与 HashMap 的区别
数据结构
- TreeMap:基于红黑树(一种自平衡二叉查找树)实现,保证元素有序存储(按键的自然顺序或自定义比较器排序)。
- HashMap:基于哈希表(数组+链表/红黑树)实现,元素存储无序。
性能对比
- 插入/删除/查找:
TreeMap:时间复杂度为 O(log n),因红黑树的平衡操作。HashMap:平均 O(1)(理想哈希冲突下),最坏 O(n)(哈希冲突严重时退化为链表)。
- 内存占用:
TreeMap:需额外存储树结构信息(左/右子节点、颜色等),内存占用较高。HashMap:仅需存储哈希桶和链表/树节点,通常更节省内存。
排序特性
- TreeMap:
- 默认按键的自然顺序(如
String按字典序,Integer按数值)排序。 - 可通过
Comparator自定义排序规则。 - 支持
firstKey()、lastKey()等有序操作。
- 默认按键的自然顺序(如
- HashMap:
- 无序存储,迭代顺序不固定(Java 8 后按桶顺序,但不保证逻辑顺序)。
使用场景
- TreeMap:
- 需要有序遍历键值对的场景(如按范围查询、排行榜)。
- 键需实现
Comparable或提供Comparator。
- HashMap:
- 高频增删查操作,且无需排序的场景(如缓存、快速查找)。
示例代码
// TreeMap 示例(自然排序)
TreeMap<Integer, String> treeMap = new TreeMap<>();
treeMap.put(3, "Three");
treeMap.put(1, "One");
treeMap.put(2, "Two");
System.out.println(treeMap); // 输出:{1=One, 2=Two, 3=Three}
// HashMap 示例(无序)
HashMap<Integer, String> hashMap = new HashMap<>();
hashMap.put(3, "Three");
hashMap.put(1, "One");
hashMap.put(2, "Two");
System.out.println(hashMap); // 输出可能为:{1=One, 2=Two, 3=Three}(但不保证)
注意事项
- 键的要求:
TreeMap的键必须实现Comparable或传入Comparator,否则抛ClassCastException。HashMap的键需正确实现hashCode()和equals()。
- 线程安全:
- 两者均非线程安全,需用
Collections.synchronizedMap或ConcurrentHashMap包装。
- 两者均非线程安全,需用
TreeMap 的线程安全性
概念定义
TreeMap 是 Java 集合框架中的一个基于红黑树(Red-Black Tree)实现的有序映射(Map)。它默认按照键的自然顺序排序,或者通过传入的 Comparator 进行自定义排序。TreeMap 本身不是线程安全的类,这意味着在多线程环境下直接使用可能会导致数据不一致或并发修改异常(ConcurrentModificationException)。
线程不安全的表现
- 并发修改问题:多个线程同时对 TreeMap 进行增删改操作时,可能导致内部红黑树结构被破坏,进而引发数据不一致或程序崩溃。
- 迭代器失效:在遍历 TreeMap 时(如使用
iterator()),如果其他线程修改了映射,会抛出ConcurrentModificationException。
如何保证线程安全
-
使用
Collections.synchronizedSortedMap
可以通过Collections.synchronizedSortedMap方法将 TreeMap 包装成线程安全的 SortedMap:SortedMap<String, Integer> syncTreeMap = Collections.synchronizedSortedMap(new TreeMap<>());- 所有操作(如
put、get、remove)会通过同步锁(synchronized)保证线程安全。 - 缺点:高并发场景下性能较差,因为所有操作会竞争同一把锁。
- 所有操作(如
-
使用
ConcurrentSkipListMap
Java 提供的ConcurrentSkipListMap是一个线程安全且有序的映射实现,基于跳表(Skip List)数据结构:ConcurrentSkipListMap<String, Integer> concurrentMap = new ConcurrentSkipListMap<>();- 支持高并发读写,性能优于
synchronizedSortedMap。 - 提供原子性操作(如
putIfAbsent、replace)。
- 支持高并发读写,性能优于
注意事项
-
手动同步
如果需要对多个操作(如“检查-修改”)保证原子性,即使使用synchronizedSortedMap也需要额外同步:synchronized (syncTreeMap) { if (!syncTreeMap.containsKey("key")) { syncTreeMap.put("key", 1); } } -
避免迭代器并发修改
遍历时需显式加锁,否则可能抛出异常:synchronized (syncTreeMap) { for (Map.Entry<String, Integer> entry : syncTreeMap.entrySet()) { // 操作 entry } } -
性能权衡
synchronizedSortedMap适合低并发场景。ConcurrentSkipListMap适合高并发且需要排序的场景,但内存占用略高。
TreeMap 的性能分析
基本性能特征
-
时间复杂度:
- 插入(put):O(log n) —— 基于红黑树的平衡特性。
- 删除(remove):O(log n) —— 需要重新平衡树结构。
- 查询(get/containsKey):O(log n) —— 二分查找树的路径。
- 遍历(迭代):O(n) —— 中序遍历所有节点。
-
空间复杂度:
- 每个节点存储键值对及额外的树结构信息(父/子节点指针、颜色标记等),空间占用略高于
HashMap。
- 每个节点存储键值对及额外的树结构信息(父/子节点指针、颜色标记等),空间占用略高于
与 HashMap 的对比
| 操作 | TreeMap (红黑树) | HashMap (哈希表) |
|---|---|---|
| 插入/删除 | O(log n) | O(1) 平均 |
| 查询 | O(log n) | O(1) 平均 |
| 有序性 | 支持(自然序/自定义) | 无序 |
适用场景
-
需要有序键的场景:
- 按键排序遍历(如输出排行榜、范围查询)。
- 使用
subMap()、headMap()、tailMap()等范围操作时高效。
-
内存敏感但需排序:
- 相比
LinkedHashMap(维护插入顺序),TreeMap在动态排序场景更节省内存。
- 相比
性能优化注意事项
-
自定义比较器的代价:
- 频繁比较的
Comparator应尽量简单(如避免字符串拼接等耗时操作)。
// 低效示例:每次比较拼接字符串 TreeMap<String, String> map = new TreeMap<>((a, b) -> (a + b).compareTo(b + a)); // 避免此写法 - 频繁比较的
-
初始容量无关:
- 与
HashMap不同,TreeMap无扩容机制,无需关注初始容量参数。
- 与
-
并发场景的替代方案:
- 多线程环境下需用
Collections.synchronizedSortedMap包装,或改用ConcurrentSkipListMap(跳表实现,并发安全且有序)。
- 多线程环境下需用
示例:性能测试对比
// 插入 100,000 个随机键值对的耗时对比
public static void main(String[] args) {
Map<Integer, String> hashMap = new HashMap<>();
Map<Integer, String> treeMap = new TreeMap<>();
long start = System.currentTimeMillis();
for (int i = 0; i < 100_000; i++) {
hashMap.put(ThreadLocalRandom.current().nextInt(), "value");
}
System.out.println("HashMap 插入耗时: " + (System.currentTimeMillis() - start) + "ms");
start = System.currentTimeMillis();
for (int i = 0; i < 100_000; i++) {
treeMap.put(ThreadLocalRandom.current().nextInt(), "value");
}
System.out.println("TreeMap 插入耗时: " + (System.currentTimeMillis() - start) + "ms");
}
典型输出:
HashMap 插入耗时: 15ms
TreeMap 插入耗时: 50ms
二、TreeMap 的核心实现
红黑树数据结构基础
红黑树定义
红黑树是一种自平衡二叉查找树,通过颜色标记和旋转操作维持平衡,保证最坏情况下基本操作(插入、删除、查找)的时间复杂度为 O(log n)。
核心性质
- 颜色属性:每个节点非红即黑。
- 根节点:根必须是黑色。
- 叶子节点:所有空节点(NIL)视为黑色。
- 红色限制:红色节点的子节点必须为黑色(即不能有连续红色节点)。
- 黑高一致:从任意节点到其子孙叶子节点的路径包含相同数量的黑色节点。
平衡机制
通过旋转(左旋/右旋)和颜色调整修复插入/删除后的性质破坏:
- 左旋:将右子节点提升为父节点。
- 右旋:将左子节点提升为父节点。
与普通二叉树的区别
| 特性 | 普通二叉树 | 红黑树 |
|---|---|---|
| 平衡性 | 可能退化为链表 | 自动平衡 |
| 查找时间复杂度 | O(n) 最坏 | O(log n) 稳定 |
| 插入/删除成本 | 低(无平衡操作) | 需额外平衡操作 |
代码示例(节点结构)
class RBNode<K extends Comparable<K>, V> {
K key;
V value;
RBNode<K, V> left, right, parent;
boolean color; // 通常 true=红,false=黑
public RBNode(K key, V value) {
this.key = key;
this.value = value;
this.color = true; // 新节点默认红色
}
}
使用场景
- 需要有序且高频更新的数据:如Java的
TreeMap、Linux进程调度。 - 避免最坏性能:当普通二叉搜索树可能退化为链表时。
注意事项
- 实现复杂度:需处理多种旋转和颜色调整情况。
- 内存开销:每个节点需额外存储颜色和父节点指针。
TreeMap 的内部结构
红黑树(Red-Black Tree)
TreeMap 基于 红黑树(一种自平衡的二叉查找树)实现。红黑树通过以下规则保证平衡性:
- 节点颜色:每个节点是红色或黑色。
- 根节点:根节点必须是黑色。
- 叶子节点(NIL):所有叶子节点(空节点)视为黑色。
- 红色节点限制:红色节点的子节点必须是黑色(即不能有连续的红色节点)。
- 黑高平衡:从任意节点到其叶子节点的路径上,黑色节点数量相同。
节点结构(Entry)
TreeMap 的节点通过静态内部类 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; // 节点颜色(默认黑色)
}
排序规则
TreeMap 通过以下两种方式维护键的有序性:
- 自然排序:键需实现
Comparable接口,通过compareTo()方法排序。 - 定制排序:构造时传入
Comparator对象,自定义比较逻辑。
操作复杂度
- 插入/删除/查找:时间复杂度为
O(log n),因红黑树的自平衡特性避免退化为链表。
关键方法实现
-
插入(put):
- 按二叉查找树规则找到插入位置。
- 新节点初始为红色,通过旋转和变色恢复平衡。
-
删除(remove):
- 找到待删除节点后,分情况处理(无子节点、单子节点、双子节点)。
- 通过调整保证删除后仍满足红黑树性质。
示例代码(自然排序)
TreeMap<String, Integer> map = new TreeMap<>();
map.put("apple", 1);
map.put("banana", 2);
System.out.println(map); // 输出:{apple=1, banana=2}(按键字母顺序排序)
Entry 节点的组成
在 TreeMap 中,Entry 是存储键值对的基本单元,同时也是红黑树的节点结构。其核心组成如下:
1. 基本字段
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; // 节点颜色(红黑树特性)
}
2. 关键特性
- 键值对存储:
key和value直接存储数据。 - 树形结构:通过
left、right、parent维护红黑树的父子关系。 - 颜色标记:
color字段(RED/BLACK)用于红黑树的平衡规则。
3. 方法实现
getKey()和getValue():直接返回键或值。setValue(V value):修改当前节点的值。equals()和hashCode():基于Map.Entry的规范实现键值对的比较。
4. 示例代码(遍历节点)
TreeMap<Integer, String> map = new TreeMap<>();
map.put(3, "Three");
map.put(1, "One");
// 获取第一个Entry节点
Entry<Integer, String> firstEntry = map.firstEntry();
System.out.println("Key: " + firstEntry.getKey() + ", Value: " + firstEntry.getValue());
5. 注意事项
- 不可变性:
key不可为null(依赖比较器或自然排序)。 - 结构依赖:修改
left/right/parent需同步维护红黑树平衡。 - 线程不安全:多线程操作需外部同步。
TreeMap 的构造方法
默认构造方法
TreeMap<K, V> map = new TreeMap<>();
- 作用:创建一个空的
TreeMap,使用键的自然顺序进行排序。 - 要求:键的类型
K必须实现Comparable接口(如String、Integer等)。 - 示例:
TreeMap<String, Integer> map = new TreeMap<>(); // 按键的字典序排序
自定义比较器的构造方法
TreeMap<K, V> map = new TreeMap<>(Comparator<? super K> comparator);
- 作用:通过传入的
Comparator自定义键的排序规则。 - 适用场景:键未实现
Comparable,或需要覆盖自然顺序时。 - 示例(按字符串长度排序):
TreeMap<String, Integer> map = new TreeMap<>( (s1, s2) -> s1.length() - s2.length() // 比较字符串长度 );
从 Map 初始化的构造方法
TreeMap<K, V> map = new TreeMap<>(Map<? extends K, ? extends V> m);
- 作用:将已有
Map的数据复制到TreeMap中,并按键的自然顺序排序。 - 注意:若原
Map的键未实现Comparable,会抛出ClassCastException。
从 SortedMap 初始化的构造方法
TreeMap<K, V> map = new TreeMap<>(SortedMap<K, ? extends V> m);
- 作用:复制
SortedMap的数据及其排序规则到新TreeMap。 - 特点:保留原
SortedMap的比较器(自然顺序或自定义顺序)。
自定义比较器的实现方式
概念定义
自定义比较器是用于定义对象排序规则的接口实现,通常通过 Comparator 接口实现。它允许开发者灵活地指定对象的比较逻辑,而不依赖对象的自然排序(如 Comparable 接口)。
使用场景
- 对没有实现
Comparable接口的类进行排序。 - 需要多种排序规则(如按年龄、姓名等不同字段排序)。
- 对集合(如
TreeMap、TreeSet)或数组进行自定义排序。
实现方式
-
匿名内部类
适用于临时、简单的比较逻辑。Comparator<String> lengthComparator = new Comparator<String>() { @Override public int compare(String s1, String s2) { return Integer.compare(s1.length(), s2.length()); } }; -
Lambda 表达式(Java 8+)
简化代码,适用于函数式编程。Comparator<String> lengthComparator = (s1, s2) -> Integer.compare(s1.length(), s2.length()); -
方法引用(Java 8+)
进一步简化,直接引用已有方法。


4449

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



