这次我们把 Java 的 TreeSet 想象成一个 “自动去重且智能排序的魔法书架” 📚。这篇文章揭秘的就是这个书架如何利用 TreeMap(我们之前讲过的红黑树魔法图书馆)的核心能力,实现元素唯一且自动排序的功能。
故事主角:你的“智能去重排序书架”(TreeSet<E>)
-
书架的本质:一个“伪装”的图书馆 (
TreeMap的华丽马甲)-
最核心的秘密:
TreeSet自己几乎不存东西!它内部藏着一个TreeMap<E, Object>。 -
怎么存元素?当你往书架 (
TreeSet) 上放一本书(元素e)时:- 书架管理员 (
TreeSet) 会拿着这本书e,跑到它内部的魔法图书馆 (TreeMap)。 - 对图书馆管理员 (
TreeMap) 说:“请把这本书e作为键(Key) 存起来,对应的值(Value) 就用这个固定的PRESENT小书签📑(一个静态空对象)”。
- 书架管理员 (
-
源码证据:
java Copy // TreeSet 的核心属性:一个 NavigableMap (实际就是 TreeMap) private transient NavigableMap<E, Object> m; // 那个固定的“小书签”值 private static final Object PRESENT = new Object(); // 添加元素 (add) public boolean add(E e) { return m.put(e, PRESENT) == null; // 关键!调用 TreeMap 的 put } -
为什么这么设计?
- 去重:
TreeMap的键 (Key) 是唯一的。如果e已经作为键存在图书馆里了,put(e, PRESENT)会返回旧的PRESENT(非null),add方法就知道重复了,返回false。如果不存在,put返回null,add返回true。 - 排序:
TreeMap天然就会根据键 (e) 的自然顺序或比较器 (Comparator) 来排序键。TreeSet继承了这种排序能力,因为它遍历的就是TreeMap的键。 - 高效: 直接复用
TreeMap强大的红黑树实现(插入、删除、查找都是O(log n)),TreeSet自己只需要做简单的包装。
- 去重:
-
-
书架管理员 (
TreeSet) 的日常工作 (核心操作揭秘)-
上架新书 (
add(e)):- 如上所述,本质是调用
TreeMap.put(e, PRESENT)。 - 红黑树的魔法(找位置、插入、变色、旋转保持平衡)都在
TreeMap.put里发生。TreeSet只关心返回值是不是null来判断是否成功(是否重复)。
- 如上所述,本质是调用
-
下架旧书 (
remove(o)):- 本质是调用
TreeMap.remove(o)。 - 如果
TreeMap.remove(o)返回了PRESENT(说明原来有这个键),TreeSet.remove就返回true(成功移除)。如果返回null,说明没这本书,返回false。 - 红黑树的删除和再平衡魔法也在
TreeMap.deleteEntry里完成。
java Copy public boolean remove(Object o) { return m.remove(o) == PRESENT; // 关键!调用 TreeMap 的 remove } - 本质是调用
-
检查书架是否有某本书 (
contains(o)):- 本质是问图书馆 (
TreeMap):“你有这个键 (o) 吗?” (TreeMap.containsKey(o))。 TreeMap.containsKey内部使用高效的二叉查找 (O(log n)) 在红黑树中查找。
java Copy public boolean contains(Object o) { return m.containsKey(o); // 关键!调用 TreeMap 的 containsKey } - 本质是问图书馆 (
-
数书 (
size()) 和 检查空书架 (isEmpty()):- 直接问图书馆 (
TreeMap) 有多少个键值对 (size) 或者是不是空的 (isEmpty)。
java Copy public int size() { return m.size(); } public boolean isEmpty() { return m.isEmpty(); } - 直接问图书馆 (
-
按顺序浏览所有书 (
iterator()):- 本质是获取
TreeMap的键集合 (navigableKeySet()) 的迭代器。 - 这个迭代器会按照键(也就是
TreeSet的元素)的排序顺序(从小到大)遍历。 - 迭代器还支持
remove,调用它实际是调用TreeMap的删除。
java Copy public Iterator<E> iterator() { return m.navigableKeySet().iterator(); // 关键!获取 TreeMap 键的迭代器 } - 本质是获取
-
-
书架的智能导航功能 (来自
NavigableSet接口)-
书架管理员 (
TreeSet) 还精通导航术!它能快速找到:- 第一本书 (
first()):书架最左边(最小)的书。调用TreeMap.firstKey()。 - 最后一本书 (
last()):书架最右边(最大)的书。调用TreeMap.lastKey()。 - 比某本书
e小的最大书 (lower(e)):调用TreeMap.lowerKey(e)。 - 比某本书
e小或等于的最大书 (floor(e)):调用TreeMap.floorKey(e)。 - 比某本书
e大或等于的最小书 (ceiling(e)):调用TreeMap.ceilingKey(e)。 - 比某本书
e大的最小书 (higher(e)):调用TreeMap.higherKey(e)。 - 拿走并返回第一本书 (
pollFirst()):调用TreeMap.pollFirstEntry().getKey()。 - 拿走并返回最后一本书 (
pollLast()):调用TreeMap.pollLastEntry().getKey()。
- 第一本书 (
-
按范围找书 (
subSet,headSet,tailSet):- 比如想找书名在 "Apple" 到 "Orange" 之间的所有书(不去动原书架)。
TreeSet调用TreeMap.subMap(...)得到一个SubMap(图书馆的某个区域视图)。- 然后用这个
SubMap创建一个新的TreeSet书架。这个新书架不是复制书籍,而是指向原图书馆的那个特定区域!非常高效。
java Copy public NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { return new TreeSet<>(m.subMap(fromElement, fromInclusive, toElement, toInclusive)); // 创建视图 }
-
-
书架的规矩与特点
-
元素必须可比 (
Comparable或Comparator): 和TreeMap的键一样,放进TreeSet的元素要么实现Comparable接口,要么在创建TreeSet时提供一个Comparator。否则书架管理员不知道怎么排序,会懵 (ClassCastException)。 -
拒绝重复 (
Set特性): 相同的书只能放一本(基于equals判断)。 -
性能 (
O(log n)): 得益于底层的红黑树 (TreeMap),添加、删除、查找、获取极值(first/last)等操作平均都是O(log n)的时间复杂度。比HashSet(O(1)) 慢,但换来了自动排序和强大的导航能力。 -
内存开销: 每个元素在
TreeMap中对应一个节点,节点存储键(元素)、值(PRESENT)、父节点、左孩子、右孩子、颜色。比HashSet的Entry(键、值、哈希、下一个)通常开销略大。 -
非线程安全: 和
TreeMap一样,这个书架设计给单线程使用。如果多个线程同时上书下书,书架可能会乱套。解决方案:- 加锁 (
synchronized): 用Collections.synchronizedSortedSet(new TreeSet<>())包装。 - 用并发书架 (
ConcurrentSkipListSet): 基于跳表实现,线程安全且有序。
- 加锁 (
-
-
什么时候用这个智能书架 (
TreeSet)?- 需要自动排序时: 比如存储学生成绩对象并按分数从高到低排。
- 需要去重且排序时: 比如从一堆杂乱数字中提取唯一值并从小到大输出。
- 需要频繁查找“邻居”元素时: 比如在一个时间线事件集合中,快速找到某个时间点之前或之后最近的事件 (
floor,ceiling)。 - 需要范围查询时: 比如查询价格在 100 到 200 之间的所有商品 (
subSet)。
总结一下,这篇文章讲的就是:
-
TreeSet是什么? 一个基于 TreeMap(红黑树) 实现的、元素唯一且自动排序的集合 (Set)。 -
核心魔法:借力
TreeMap。TreeSet自己是个“薄薄的外壳”,它把元素 (E) 当作TreeMap的键(Key) 存储,并用一个固定的PRESENT对象作为值(Value)。所有核心功能(排序、去重、查找、导航)都委托给TreeMap完成。 -
排序规则: 必须提供元素的 自然顺序 (
Comparable) 或 自定义比较器 (Comparator)。 -
核心操作原理:
add(e)->TreeMap.put(e, PRESENT)(利用键唯一性去重)remove(o)->TreeMap.remove(o)(检查返回值是否是PRESENT)contains(o)->TreeMap.containsKey(o)(高效二叉查找)first/last/lower/floor/ceiling/higher-> 调用对应的TreeMap.firstKey/lastKey/lowerKey/floorKey/ceilingKey/higherKeysubSet/headSet/tailSet-> 创建基于TreeMap子映射 (SubMap) 的TreeSet视图 (高效)iterator()-> 获取TreeMap键集合的迭代器 (按顺序遍历)
-
关键特性:
- 有序且唯一。
- 非线程安全。
- 元素必须可比 (
Comparable/Comparator)。 - 性能:核心操作平均
O(log n)。比HashSet(O(1)) 慢,但比无序的List查找 (O(n)) 快得多,且自带排序。 - 空间:比
HashSet开销略大 (红黑树节点结构)。
-
为什么用它? 当你需要 元素唯一 且 自动排序,或者需要 高效的极值/邻居查找/范围查询 时,
TreeSet是你的首选!
一句话记住 TreeSet:它就像一个聪明又省事的图书管理员,内部雇佣了红黑树魔法图书馆 (TreeMap) 来干活,自己只负责保证你放的书不重复,并且总是按顺序摆得整整齐齐,还能快速帮你找到任何位置的书! 📚✨ 下次需要有序且唯一的集合,就交给 TreeSet 吧!