JDK 8 中的 TreeSet
是基于 TreeMap
实现的 有序集合,它继承自 AbstractSet
并实现了 NavigableSet
接口(NavigableSet
继承自 SortedSet
)。TreeSet
的核心特性是元素唯一且按自然顺序或自定义比较器排序,并支持高效的导航操作(如查找最接近的元素)。
一、TreeSet 的核心特性
特性 | 说明 |
---|---|
有序性 | 元素按键的自然顺序(若元素实现 Comparable )或自定义 Comparator 排序。 |
元素唯一 | 元素不可重复(依赖 TreeMap 键的唯一性)。 |
不允许 null 元素 | 若使用自然排序(无 Comparator ),元素必须非 null (因 Comparable 不允许 null );若使用自定义 Comparator ,可能允许 null (但需 Comparator 处理 null )。 |
导航方法 | 支持 ceiling() 、floor() 、higher() 、lower() 等方法,快速查找最接近的元素。 |
时间复杂度 | 插入、删除、查找等操作的平均/最坏时间复杂度为 O(log n) (基于红黑树的高度)。 |
二、TreeSet 的核心结构
1. 继承与接口
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable
NavigableSet
:扩展了SortedSet
,增加了导航方法(如查找最近元素)和视图操作(如子集合)。AbstractSet
:提供Set
接口的骨架实现,减少重复代码。
2. 核心属性
TreeSet
内部通过 TreeMap
存储元素,键为集合元素,值为静态的 Object
占位符(仅用于标记存在):
// 内部 TreeMap 实例(存储元素)
private transient NavigableMap<E, Object> m;
// 值的占位符(无实际意义,仅用于 TreeMap 存储)
private static final Object PRESENT = new Object();
3. 构造方法
TreeSet
的构造方法本质是初始化内部的 TreeMap
:
// 默认构造方法:使用自然排序
public TreeSet() {
this(new TreeMap<>()); // 内部 TreeMap 使用自然排序
}
// 指定 Comparator 的构造方法
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator)); // 内部 TreeMap 使用自定义比较器
}
// 从 Iterable 初始化(按自然排序或 Comparator 排序)
public TreeSet(Collection<? extends E> c) {
this();
addAll(c); // 插入所有元素,触发排序
}
// 从 SortedSet 初始化(保留排序规则)
public TreeSet(SortedSet<E> s) {
this(s.comparator()); // 继承原 SortedSet 的比较器
addAll(s); // 插入所有元素
}
三、核心方法解析
TreeSet
的方法本质是对内部 TreeMap
的封装,核心逻辑依赖 TreeMap
的实现。
1. add(E e):添加元素
add
方法通过 TreeMap.put(e, PRESENT)
实现,利用 TreeMap
键的唯一性保证元素不重复:
public boolean add(E e) {
return m.put(e, PRESENT) == null; // TreeMap 的 put 返回旧值(若存在则返回旧值,否则返回 null)
}
2. contains(Object o):判断元素是否存在
通过 TreeMap.containsKey(o)
实现,依赖 TreeMap
的键查找逻辑:
public boolean contains(Object o) {
return m.containsKey(o); // TreeMap 的 containsKey 基于红黑树查找
}
3. remove(Object o):删除元素
通过 TreeMap.remove(o)
实现,删除成功返回 true
(若元素存在):
public boolean remove(Object o) {
return m.remove(o) == PRESENT; // TreeMap 的 remove 返回旧值(存在则返回 PRESENT,否则返回 null)
}
4. iterator():返回迭代器
TreeSet
的迭代器按元素的自然顺序或 Comparator
顺序遍历,通过 TreeMap.keySet().iterator()
实现:
public Iterator<E> iterator() {
return m.navigableKeySet().iterator(); // 返回 TreeMap 键集的迭代器(有序)
}
5. 导航方法(NavigableSet 接口)
TreeSet
实现了 NavigableSet
的导航方法,直接调用内部 TreeMap
的对应方法:
方法 | 说明 |
---|---|
ceiling(E e) | 返回大于等于 e 的最小元素(若不存在则返回 null )。 |
floor(E e) | 返回小于等于 e 的最大元素(若不存在则返回 null )。 |
higher(E e) | 返回大于 e 的最小元素(若不存在则返回 null )。 |
lower(E e) | 返回小于 e 的最大元素(若不存在则返回 null )。 |
ceilingEntry(E e) | (内部 TreeMap 方法)返回大于等于 e 的最小键值对。 |
subSet(E fromElement, E toElement) | 返回 [fromElement, toElement) 范围内的子集合(视图)。 |
headSet(E toElement) | 返回小于 toElement 的子集合(视图)。 |
tailSet(E fromElement) | 返回大于等于 fromElement 的子集合(视图)。 |
示例:导航方法的使用
TreeSet<Integer> set = new TreeSet<>(Arrays.asList(1, 3, 5, 7, 9));
System.out.println(set.ceiling(4)); // 5(大于等于4的最小元素)
System.out.println(set.floor(6)); // 5(小于等于6的最大元素)
System.out.println(set.higher(5)); // 7(大于5的最小元素)
System.out.println(set.lower(5)); // 3(小于5的最大元素)
四、关键源码细节
1. 内部 TreeMap 的初始化
TreeSet
的构造方法通过传入 TreeMap
实例完成初始化,TreeMap
的比较器决定了 TreeSet
的排序规则:
private transient NavigableMap<E, Object> m;
// 默认构造方法调用此构造器,传入自然排序的 TreeMap
TreeSet(NavigableMap<E, Object> m) {
this.m = m;
}
2. 子集合(View)的实现
TreeSet
的 subSet
、headSet
、tailSet
方法返回的是 TreeSet
的视图(View
),共享原 TreeSet
的底层 TreeMap
,因此对视图的修改会直接影响原集合:
public NavigableSet<E> subSet(E fromElement, E toElement) {
return new TreeSet<>(m.subMap(fromElement, true, toElement, false));
}
public NavigableSet<E> headSet(E toElement) {
return new TreeSet<>(m.headMap(toElement, false));
}
public NavigableSet<E> tailSet(E fromElement) {
return new TreeSet<>(m.tailMap(fromElement, true));
}
TreeMap.subMap
等方法返回的是SubMap
内部类实例,通过限制键的范围实现视图效果。
五、红黑树的依赖(TreeMap 的底层逻辑)
TreeSet
的有序性和高效操作最终依赖于 TreeMap
内部的红黑树实现。红黑树通过以下机制保证平衡:
- 节点颜色:每个节点标记为红色或黑色,根节点和叶子节点(
NIL
)为黑色。 - 平衡规则:无连续红色节点,任意路径的黑节点数量相同。
- 旋转与变色:插入或删除节点后,通过左旋、右旋和变色操作恢复平衡。
六、注意事项
- 元素必须可比较:若使用自然排序,元素必须实现
Comparable
接口(如Integer
、String
),否则插入时会抛出ClassCastException
。TreeSet<Person> set = new TreeSet<>(); // Person 未实现 Comparable,编译错误!
- null 元素的限制:自然排序下
TreeSet
不允许null
元素(Comparable
接口禁止null
);自定义Comparator
若允许null
,需显式处理(否则可能抛出NullPointerException
)。 - 性能对比:
TreeSet
的插入、删除、查找时间复杂度为O(log n)
,适合需要有序性和导航的场景;HashSet
的平均时间复杂度为O(1)
,适合仅需快速查找的场景。
七、总结
TreeSet
是基于 TreeMap
实现的有序集合,核心优势是元素唯一和有序性,并支持高效的导航操作。其内部通过 TreeMap
存储元素(键为集合元素,值为占位符),排序规则由 TreeMap
的比较器决定。实际开发中,若需要有序集合且需频繁进行范围查询或导航操作,TreeSet
是首选;若只需快速去重或查找,HashSet
更高效。