这次我们把 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/higherKey
subSet/headSet/tailSet
-> 创建基于TreeMap
子映射 (SubMap
) 的TreeSet
视图 (高效)iterator()
-> 获取TreeMap
键集合的迭代器 (按顺序遍历)
-
关键特性:
- 有序且唯一。
- 非线程安全。
- 元素必须可比 (
Comparable
/Comparator
)。 - 性能:核心操作平均
O(log n)
。比HashSet
(O(1)
) 慢,但比无序的List
查找 (O(n)
) 快得多,且自带排序。 - 空间:比
HashSet
开销略大 (红黑树节点结构)。
-
为什么用它? 当你需要 元素唯一 且 自动排序,或者需要 高效的极值/邻居查找/范围查询 时,
TreeSet
是你的首选!
一句话记住 TreeSet
:它就像一个聪明又省事的图书管理员,内部雇佣了红黑树魔法图书馆 (TreeMap
) 来干活,自己只负责保证你放的书不重复,并且总是按顺序摆得整整齐齐,还能快速帮你找到任何位置的书! 📚✨ 下次需要有序且唯一的集合,就交给 TreeSet
吧!