在日常开发中,我们经常会遇到动态查找 + 键有序的场景 —— 比如按用户等级排序展示信息、统计商品销量后按销量降序排列、管理有序的配置项等。此时,HashMap/HashSet 虽然能提供 O (1) 的高效操作,但 “无序” 的特性会让后续排序成本陡增;而普通二叉搜索树又可能因插入顺序问题退化成单支树,导致性能骤降。
TreeMap 与 TreeSet 正是为解决这类问题而生 —— 它们底层基于红黑树(一种近似平衡的二叉搜索树),既保证了 Key 的有序性,又将插入、删除、查找的时间复杂度稳定在 O (logN) 级别。今天我们就来拆解这两个集合类的核心逻辑、使用技巧与实战场景。
一、先搞懂底层:红黑树
在聊 TreeMap 和 TreeSet 之前,必须先提它们的 “基石”—— 红黑树。因为 TreeMap/TreeSet 的所有特性(有序、高效)都源于红黑树的设计:
红黑树是一种自平衡的二叉搜索树,在普通二叉搜索树的基础上增加了 “颜色规则”(每个节点非红即黑),通过旋转和颜色调整,确保树的高度始终维持在 O (logN) 级别(近似平衡)。这就解决了普通二叉搜索树可能退化成 “单支树”(时间复杂度 O (N))的痛点。
简单来说,红黑树的核心作用是:保证 Key 的有序性(二叉搜索树特性)+ 稳定的高效操作(平衡特性) —— 这也是 TreeMap/TreeSet 的核心优势。
二、TreeMap:有序键值对的 “管理器”
TreeMap 是 Map 接口的实现类,专门存储<K, V>键值对,核心特点是Key 有序且唯一。
1. TreeMap 的核心特性
基于红黑树的底层设计,TreeMap 有以下关键特性,这些也是开发中必须注意的点:
- Key 唯一,Value 可重复:红黑树的特性确保 Key 不会重复(插入重复 Key 会覆盖 Value),但 Value 没有唯一性限制。
- Key 不能为空,Value 可以为空:若插入 Key 为
null,会直接抛出NullPointerException;但 Value 允许为null。 - Key 有序:默认按 Key 的 “自然顺序”(如 Integer 升序、String 字典序)排列,也可通过自定义
Comparator指定排序规则。 - 线程不安全:与 HashMap 类似,TreeMap 没有同步机制,多线程环境下需手动加锁(如使用
Collections.synchronizedSortedMap)。 - 查找效率稳定:插入、删除、查找的时间复杂度均为 O (logN),不受数据插入顺序影响(红黑树的平衡特性保障)。
2. TreeMap 的常用方法与实战
TreeMap 提供了 Map 接口的所有核心方法,这里挑几个高频使用的重点讲解,并结合实战案例说明:
| 方法 | 核心作用 | 注意点 |
|---|---|---|
V put(K key, V value) | 插入键值对:Key 不存在则插入,返回null;Key 存在则覆盖 Value,返回旧 Value | 触发红黑树平衡调整 |
V get(Object key) | 根据 Key 获取 Value,Key 不存在返回null | 按红黑树规则查找,效率 O (logN) |
V getOrDefault(Object key, V defaultValue) | 同上,但 Key 不存在时返回默认值 | 避免手动判空,简化代码 |
boolean containsKey(Object key) | 判断是否包含指定 Key | 效率 O (logN)(红黑树查找) |
boolean containsValue(Object value) | 判断是否包含指定 Value | 效率 O (N)(需遍历所有 Value) |
Set<K> keySet() | 返回所有 Key 的有序 Set 集合 | 迭代顺序与 Key 的排序规则一致 |
Set<Map.Entry<K, V>> entrySet() | 返回所有键值对的 Set 集合 | 遍历键值对的最优方式(避免二次查找) |
实战案例:用 TreeMap 管理 “学生成绩排行榜”
需求:存储学生姓名(Key)与分数(Value),按分数降序排列,若分数相同则按姓名升序排列。
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
public class TreeMapDemo {
public static void main(String[] args) {
//1. 初始化TreeMap,指定自定义比较器(分数降序,姓名升序)
Map<String, Integer> scoreMap = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String name1, String name2) {
//先获取两个学生的分数
int score1 = scoreMap.getOrDefault(name1, 0);
int score2 = scoreMap.getOrDefault(name2, 0);
//分数降序:分数高的在前
if (score2 != score1) {
return score2 - score1;
}
//分数相同则按姓名升序(字典序)
return name1.compareTo(name2);
}
});
//2. 插入数据
scoreMap.put("张三", 95);
scoreMap.put("李四", 88);
scoreMap.put("王五", 95); //与张三分数相同,按姓名排序
scoreMap.put("赵六", 92);
//3. 遍历键值对(有序输出)
System.out.println("学生成绩排行榜:");
for (Map.Entry<String, Integer> entry : scoreMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue() + "分");
}
//4. 常用操作示例
System.out.println("\n张三的分数:" + scoreMap.get("张三")); //95
System.out.println("是否包含孙七?" + scoreMap.containsKey("孙七")); //false
System.out.println("孙七的分数(默认0):" + scoreMap.getOrDefault("孙七", 0)); //0
}
}
输出结果会按 “分数降序 + 姓名升序” 排列,符合我们的需求:

三、TreeSet:有序去重的 “筛选器”
TreeSet 是 Set 接口的实现类,核心作用是存储唯一、有序的 Key,可以理解为 “只存 Key 的 TreeMap”。
1. TreeSet 的核心特性
TreeSet 的底层是基于 TreeMap 实现的(将 TreeSet 的 Key 作为 TreeMap 的 Key,Value 用一个固定的空对象PRESENT填充),因此它的特性与 TreeMap 高度一致:
- 只存 Key,且 Key 唯一:天然支持去重(插入重复 Key 会返回
false,不生效)。 - Key 不能为空:插入
null会抛出NullPointerException。 - Key 有序:默认按自然顺序,也可通过
Comparator自定义排序。 - 线程不安全:无同步机制,多线程需手动处理。
- 操作效率 O (logN):依赖 TreeMap 的红黑树实现,插入、删除、查找均为 O (logN)。
2. TreeSet 的常用方法与实战
TreeSet 继承自 Collection,常用方法聚焦于 “添加、判断、删除” 等 Set 核心操作:
| 方法 | 核心作用 | 注意点 |
|---|---|---|
boolean add(E e) | 添加元素:元素不存在则插入,返回true;存在则返回false | 去重的核心方法 |
boolean contains(Object o) | 判断元素是否存在,存在返回true | 效率 O (logN) |
boolean remove(Object o) | 删除元素:存在则删除并返回true;不存在返回false | 需注意红黑树的平衡调整 |
Iterator<E> iterator() | 返回有序迭代器 | 迭代顺序与 Key 的排序规则一致 |
int size() | 返回元素个数 | 直接获取,O (1) 效率 |
void clear() | 清空所有元素 | 清空红黑树,O (N) 效率 |
实战案例:用 TreeSet 实现 “有序去重的商品分类”
需求:存储商品分类名称,要求去重且按 “分类名称长度升序” 排列(长度相同则按字典序)。
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
//1. 初始化TreeSet,自定义排序规则:按名称长度升序,长度相同按字典序
TreeSet<String> categorySet = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String c1, String c2) {
//先按长度升序
if (c1.length() != c2.length()) {
return c1.length() - c2.length();
}
//长度相同按字典序
return c1.compareTo(c2);
}
});
//2. 添加元素(测试去重:重复的“电子产品”不会被插入)
categorySet.add("电子产品");
categorySet.add("服装");
categorySet.add("食品");
categorySet.add("电子产品"); //重复元素,添加失败
categorySet.add("家居用品");
//3. 遍历(有序输出)
System.out.println("有序去重的商品分类:");
Iterator<String> iterator = categorySet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//4. 常用操作示例
System.out.println("\n是否包含“化妆品”?" + categorySet.contains("化妆品")); //false
System.out.println("删除“食品”:" + categorySet.remove("食品")); //true
System.out.println("删除后分类:" + categorySet);
}
}
输出结果会按 “长度升序 + 字典序” 去重排列:

四、TreeMap 与 TreeSet 的关联与区别
1. 核心关联:TreeSet 依赖 TreeMap 实现
TreeSet 的底层源码非常简单 —— 它内部持有一个 TreeMap 实例,将所有添加的元素作为 TreeMap 的 Key,Value 则用一个静态常量PRESENT(private static final Object PRESENT = new Object())填充。
也就是说:
- TreeSet 的 “去重” 本质是 TreeMap 的 Key 唯一。
- TreeSet 的 “有序” 本质是 TreeMap 的 Key 有序。
- TreeSet 的所有操作(add、remove、contains)最终都委托给 TreeMap 的对应方法。
2. 核心区别:存储模型不同
| 维度 | TreeMap | TreeSet |
|---|---|---|
| 存储模型 | <K, V>键值对 | 仅 Key(纯 Key 模型) |
| 核心用途 | 需关联 Key 与 Value 的场景(如排行榜、配置表) | 仅需去重 + 有序的场景(如分类、标签) |
| 特有方法 | get(K key)、entrySet()等 Map 专属方法 | 无特有方法,仅 Set 接口方法 |
五、TreeMap/TreeSet vs HashMap/HashSet:该怎么选?
很多时候我们会纠结:到底用 Tree 系列还是 Hash 系列?其实核心看 “是否需要有序”—— 这是两者的核心分水岭。
| 对比维度 | TreeMap/TreeSet | HashMap/HashSet |
|---|---|---|
| 底层结构 | 红黑树 | 哈希桶(数组 + 链表 / 红黑树) |
| 时间复杂度 | 插入 / 删除 / 查找均为 O (logN) | 平均 O (1),最坏 O (logN)(链表转红黑树后) |
| Key 有序性 | 有序(自然顺序 / 自定义比较器) | 无序(JDK8 后为插入顺序,非自然顺序) |
| Key 是否允许 null | 不允许(抛 NPE) | 允许(HashMap/HashSet 均可存 1 个 null Key) |
| 比较逻辑 | 依赖Comparable或Comparator | 依赖hashCode()和equals() |
| 核心场景 | 需有序的动态查找(如排行榜、排序数据) | 无需有序,追求极致性能(如缓存、高频查询) |
六、使用 TreeMap/TreeSet 的注意事项
-
自定义 Key 必须实现比较逻辑若用自定义类(如
User、Book)作为 Key,必须要么让类实现Comparable接口(重写compareTo方法),要么在初始化 TreeMap/TreeSet 时传入Comparator—— 否则插入元素时会抛出ClassCastException(无法比较 Key)。 -
Key 不可直接修改TreeMap/TreeSet 的 Key 有序性依赖 Key 本身的值,若直接修改 Key 的属性(如将
User的id从 1 改为 10),会破坏红黑树的结构,导致后续查找、删除异常。正确做法是:先删除旧 Key,再插入新 Key。 -
containsValue效率低TreeMap 的containsValue方法需要遍历所有 Value(O (N) 效率),若需频繁判断 Value 是否存在,建议反向维护一个TreeMap<V, K>(空间换时间)。 -
线程不安全需手动同步多线程环境下,若需并发操作 TreeMap/TreeSet,需使用
Collections.synchronizedSortedMap或Collections.synchronizedSortedSet包装,或使用ConcurrentSkipListMap(JUC 提供的并发有序 Map)。
总结
TreeMap 与 TreeSet 是 Java 集合框架中 “有序集合” 的代表,它们基于红黑树解决了 “有序 + 高效” 的核心需求:
- TreeMap:用
<K, V>键值对管理有序数据,适合需要关联 Key 与 Value 的场景(如排行榜、配置表)。 - TreeSet:用纯 Key 模型实现去重 + 有序,适合仅需筛选有序唯一数据的场景(如分类、标签)。
选择时记住一个核心原则:需要有序用 Tree 系列,追求性能用 Hash 系列。掌握它们的底层逻辑和使用场景,能让我们在处理动态查找问题时更加得心应手。
1413

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



