# TreeSet简介
# 1 | 概述
java.util.TreeSet<E>
是 可自动排序、不允许重复元素 的集合,实现了 NavigableSet<E>
、Cloneable
、Serializable
。
核心特点:
特性 | 说明 |
---|---|
有序 | 元素按 自然顺序(Comparable )或 自定义 Comparator 升序排列 |
底层结构 | 红黑树(内部其实是包装了一棵 TreeMap<E,Object> ) |
操作复杂度 | 增删查 O(log n) |
接口丰富 | 支持最值、邻近、范围视图、反序视图等导航操作 |
# 2 | 底层原理 & 数据结构
# 2.1 红黑树 (Red‑Black Tree)
每个节点保存:
key
、左子/右子、父节点、颜色位插入/删除后通过 左旋/右旋 + 重新着色 保证:
- 根为黑
- 所有叶子(NIL)为黑
- 红节点的子节点都为黑
- 根到每个叶子路径的黑节点数相同
树高 ≤
2 log2(n + 1)
⇒ 操作最坏O(log n)
# 2.2 TreeSet = TreeMap ⤴︎
public class TreeSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable {
private transient NavigableMap<E,Object> m;
private static final Object PRESENT = new Object();
...
public boolean add(E e) { return m.put(e, PRESENT)==null; }
}
# 3 | 排序规则
- 默认:元素必须实现
Comparable<E>
,按compareTo
升序。 - 自定义:构造时传入
Comparator<? super E>
;允许存储 “不具可比性” 的对象或自定义排序(降序、按长度等)。
⚠️ 无论哪种排序,一旦确定就应“不可变”:比较规则改变或元素自身值变动会破坏树形结构。
# 4 | 常用 API 速览
分类 | 方法 | 说明 |
---|---|---|
增删查 | add(e) / remove(o) / contains(o) | |
addAll(c) / clear() / size() | ||
最值 | first() / last() | 最小 / 最大 |
邻近 | lower(e) / higher(e) | <e / >e |
floor(e) / ceiling(e) | ≤e / ≥e | |
区间视图 | subSet(fromIncl, toExcl) | [from, to) |
headSet(toExcl) / tailSet(fromIncl) | ||
反序 | descendingSet() / descendingIterator() | |
遍历 | iterator() (升序) | Fail‑fast |
并行流 (8+) | stream() / parallelStream() |
# 5 | 复杂度与性能
操作 | 复杂度 | 说明 |
---|---|---|
add / remove / contains | O(log n) | 红黑树查找深度 |
first / last / floor / ceiling | O(log n) | |
遍历 (iterator ) | O(n) | 元素按排序顺序输出 |
# 5.1 TreeSet
vs. 其他集合
需求 | 首选 |
---|---|
唯一性 + 无序快速查找 | HashSet (O(1) 平均) |
唯一性 + 有序/邻近/区间 | TreeSet (O(log n) ) |
并发 + 有序 | ConcurrentSkipListSet (跳表) |
常数因子:
TreeSet
内部节点对象多 1 个引用 + 1 byte 颜色,占用更高,速度略低于HashSet
。
# 6 | 线程安全 & Fail‑fast
- 非线程安全:多线程并发写需显式加锁或使用
Collections.synchronizedSortedSet(set)
包装,或改用ConcurrentSkipListSet
。 - Fail‑fast:迭代期间结构若被 其他线程或方法 修改,迭代器抛
ConcurrentModificationException
(早期发现 BUG,不保证强一致)。
# 7 | 易踩坑注意事项
⚠️ 事项 | 说明 |
---|---|
禁止 null 元素 | 若使用自然顺序,插入 null 抛 NullPointerException (JDK ≥ 1.8);若自定义 Comparator 必须显式处理 null 。 |
元素可变性 | 将“会变的字段”作为排序关键时,修改后集合顺序被破坏,影响查找——典型 坑。 ➜ 元素应当不可变或 删除‑修改‑再插入。 |
比较器一致性 | compare(a,b)==0 必须等价于 a.equals(b) ,否则“看似重复”元素会并存。 |
子视图联动 | 通过 subSet / headSet 返回的是 实时视图,对其操作会同步影响原集合,反之亦然。 |
# 8 | 典型使用场景
最近值 / 最近房价
int target = 100; Integer lo = set.floor(target); // <=100 的最大 Integer hi = set.ceiling(target); // >=100 的最小
滑动窗口 + 单调有序 统计区间最小/最大值:维护
TreeSet<Integer>
或TreeMap<Integer, Integer>
计数。区间调度 会议室安排、日程冲突检测:用
floor
查看前一场结束时间是否 > 新场开始。去重排序 读取大量字符串后按字典序输出且无重复。
近似搜索 / 前缀索引
subSet("abc", "abd")
获取所有"abc*"
前缀匹配词。合并区间 / 连通块 利用
lower
/floor
找相邻区间,快速合并。
# 9 | 示例代码
import java.util.*;
public class TreeSetDemo {
public static void main(String[] args) {
// 1⃣️ 价格排行榜(升序)
TreeSet<Integer> prices = new TreeSet<>(Arrays.asList(105, 98, 120, 110));
System.out.println("最低价:" + prices.first()); // 98
System.out.println("最高价:" + prices.last()); // 120
// 2⃣️ 找最接近 108 的价格
int target = 108;
Integer lower = prices.floor(target); // 105
Integer higher = prices.ceiling(target); // 110
System.out.println(lower + ", " + higher);
// 3⃣️ 区间视图:100~115 的价格
System.out.println(prices.subSet(100, true, 115, true)); // [105, 110]
// 4⃣️ 滑动窗口最大值 (k=3):
int[] nums = {4, 2, 12, 3, 8, 5};
int k = 3;
TreeMap<Integer, Integer> window = new TreeMap<>();
for (int i = 0; i < nums.length; i++) {
window.merge(nums[i], 1, Integer::sum);
if (i >= k) { // 移除窗口左端
int out = nums[i - k];
window.merge(out, -1, (old, n) -> old + n == 0 ? null : old + n);
}
if (i >= k - 1) System.out.print(window.lastKey() + " "); // 12 12 12 8
}
}
}
# 10 | 总结
- 红黑树实现的有序集合:所有操作
O(log n)
,支持最值/邻近/区间检索 - 应用:最近值查询、窗口最值、日程冲突检测、去重排序
- 注意:禁止
null
、避免“可变键”、多线程需同步 - 与
HashSet
比:牺牲常数速度,换来 排序 + 导航;与PriorityQueue
比:支持删除任意元素与邻近搜索 - 对于并发高读写且需有序,可选
ConcurrentSkipListSet
(跳表)。
🌟 如果你需要“唯一 + 排序 + 范围操作”这三件套,
TreeSet
基本就是 Java 标准库里最顺手的工具。