目录
[1. 自然排序(默认排序)](#1. 自然排序(默认排序))
[示例:自然排序(Integer 类型,默认升序)](#示例:自然排序(Integer 类型,默认升序))
[2. 定制排序(显式指定排序规则)](#2. 定制排序(显式指定排序规则))
[示例:定制排序(Lambda 表达式简化)](#示例:定制排序(Lambda 表达式简化))
[五、常用 API(核心操作)](#五、常用 API(核心操作))
[1. 新增操作](#1. 新增操作)
[2. 查询操作(有序专属核心)](#2. 查询操作(有序专属核心))
[3. 删除操作](#3. 删除操作)
[4. 遍历操作](#4. 遍历操作)
[示例:常用 API 综合使用](#示例:常用 API 综合使用)
你希望深入掌握 Java 中TreeSet的核心特性、底层实现、排序规则、常用 API 及使用场景,我会从基础到进阶逐步拆解,结合可运行示例帮你彻底理解这个有序无重复的集合。
一、核心定位
TreeSet 是 Java 集合框架中 SortedSet 接口的唯一实现类(基于红黑树) ,隶属于 java.util 包,核心特性是元素有序且无重复,适用于需要对集合元素进行排序和去重的场景(如排行榜、有序字典等)。
二、核心特性
- 底层实现 :基于
TreeMap实现(底层是红黑树,一种自平衡的二叉查找树),TreeSet的元素实际存储在TreeMap的key位置,value固定为一个静态常量PRESENT(new Object()),利用TreeMap的键唯一性实现去重,利用红黑树结构实现有序。 - 有序性 :元素并非按插入顺序排列,而是按指定排序规则进行排序(支持两种排序方式:自然排序 + 定制排序),排序后的数据结构为有序集合(升序默认,可通过定制排序改为降序)。
- 无重复性 :通过排序规则判断元素是否重复,而非
equals()方法(若两个元素通过排序规则比较返回0,则视为重复元素,无法加入TreeSet)。 - 非线程安全 :多线程环境下并发修改
TreeSet(如同时添加 / 删除元素)会导致数据不一致,需手动保证线程安全(如使用Collections.synchronizedSortedSet(new TreeSet<>()))。 - 不支持 null 元素(默认) :自然排序下,
TreeSet不允许添加null元素(会抛出NullPointerException);定制排序中可手动支持null,但不推荐(易引发逻辑混乱)。 - 时间复杂度 :基于红黑树的特性,增、删、查(包含 / 获取)操作的时间复杂度均为
O(log n)(优于HashSet的O(1),但需付出排序成本)。 - 元素不可修改(影响排序的字段) :若自定义对象存入
TreeSet后,修改了影响排序规则的字段,会导致元素在红黑树中的位置失效,无法正确查找、删除或排序(避坑关键)。
三、两种排序规则(核心重点)
TreeSet 的有序性依赖排序规则,提供两种排序方式,不可同时使用(优先使用定制排序)。
1. 自然排序(默认排序)
适用场景
元素类型本身实现了 java.lang.Comparable 接口(JDK 自带类如 String、Integer、Long 等均已实现该接口),默认按升序排列。
核心原理
元素通过 Comparable 接口的 int compareTo(T o) 方法进行比较:
- 返回正数:当前元素 > 目标元素
- 返回 0:当前元素 = 目标元素(视为重复,无法添加)
- 返回负数:当前元素 < 目标元素
示例:自然排序(Integer 类型,默认升序)
import java.util.TreeSet;
public class TreeSetNaturalSortDemo {
public static void main(String[] args) {
TreeSet<Integer> treeSet = new TreeSet<>();
// 乱序添加元素
treeSet.add(3);
treeSet.add(1);
treeSet.add(5);
treeSet.add(2);
treeSet.add(3); // 重复元素,无法添加
// 遍历:输出有序(升序)
System.out.println("自然排序(升序):" + treeSet); // 输出:[1, 2, 3, 5]
// 验证有序性
System.out.println("第一个元素(最小):" + treeSet.first()); // 1
System.out.println("最后一个元素(最大):" + treeSet.last()); // 5
}
}
自定义类实现自然排序
若自定义类需使用自然排序,必须实现 Comparable 接口并重写 compareTo 方法:
import java.util.TreeSet;
// 自定义用户类,按年龄升序自然排序
class User implements Comparable<User> {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
// 重写compareTo方法:按年龄升序排序,年龄相同视为重复
@Override
public int compareTo(User o) {
return this.age - o.age; // 升序(o.age - this.age 为降序)
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
public class TreeSetCustomNaturalSort {
public static void main(String[] args) {
TreeSet<User> userSet = new TreeSet<>();
userSet.add(new User("张三", 25));
userSet.add(new User("李四", 22));
userSet.add(new User("王五", 25)); // 年龄与张三相同,视为重复,无法添加
userSet.add(new User("赵六", 30));
// 遍历:按年龄升序排列
System.out.println("自定义类自然排序:");
userSet.forEach(System.out::println);
// 输出:
// User{name='李四', age=22}
// User{name='张三', age=25}
// User{name='赵六', age=30}
}
}
2. 定制排序(显式指定排序规则)
适用场景
- 元素类型未实现
Comparable接口(避免修改原有类结构); - 不满足自然排序的规则(如需要降序排列、按自定义字段复杂排序)。
核心原理
创建 TreeSet 时,通过构造方法传入 java.util.Comparator 接口的实现类(匿名内部类 / Lambda 表达式),重写 int compare(T o1, T o2) 方法自定义排序规则:
- 返回正数:
o1 > o2 - 返回 0:
o1 = o2(视为重复元素,无法添加) - 返回负数:
o1 < o2
示例:定制排序(Lambda 表达式简化)
import java.util.Comparator;
import java.util.TreeSet;
// 自定义商品类(未实现Comparable接口)
class Goods {
private String name;
private double price;
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Goods{name='" + name + "', price=" + price + "}";
}
}
public class TreeSetCustomSortDemo {
public static void main(String[] args) {
// 1. 定制排序:按商品价格降序排列(Lambda 表达式简化 Comparator)
TreeSet<Goods> goodsSet = new TreeSet<>((o1, o2) -> {
// 价格降序:o2.price - o1.price(升序则反之)
return Double.compare(o2.getPrice(), o1.getPrice());
});
// 2. 添加元素
goodsSet.add(new Goods("手机", 5999.99));
goodsSet.add(new Goods("电脑", 9999.99));
goodsSet.add(new Goods("平板", 3999.99));
goodsSet.add(new Goods("手表", 5999.99)); // 价格与手机相同,视为重复,无法添加
// 3. 遍历:按价格降序排列
System.out.println("商品价格降序排列:");
goodsSet.forEach(System.out::println);
// 输出:
// Goods{name='电脑', price=9999.99}
// Goods{name='手机', price=5999.99}
// Goods{name='平板', price=3999.99}
}
}
四、构造方法
TreeSet 提供 4 个构造方法,核心是指定排序规则或初始化元素:
| 构造方法签名 | 功能说明 |
|---|---|
TreeSet() |
无参构造:默认采用自然排序 ,元素需实现 Comparable 接口 |
TreeSet(Comparator<? super E> comparator) |
定制排序构造:传入 Comparator 实现类,自定义排序规则 |
TreeSet(Collection<? extends E> c) |
集合初始化构造:将指定 Collection 元素转入 TreeSet,采用自然排序 |
TreeSet(SortedSet<E> s) |
有序集合初始化构造:继承传入 SortedSet 的排序规则,快速创建同规则 TreeSet |
示例:集合初始化构造
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
public class TreeSetConstructorDemo {
public static void main(String[] args) {
// 1. 先创建一个乱序有重复的 List
List<Integer> list = new ArrayList<>();
list.add(5);
list.add(2);
list.add(8);
list.add(2);
list.add(5);
// 2. 通过 List 初始化 TreeSet(自然排序 + 去重)
TreeSet<Integer> treeSet = new TreeSet<>(list);
System.out.println("List 初始化 TreeSet:" + treeSet); // 输出:[2, 5, 8]
}
}
五、常用 API(核心操作)
TreeSet 继承 SortedSet 接口,提供了普通集合的通用 API,还新增了有序集合专属的操作方法,核心 API 如下:
1. 新增操作
| 方法 | 功能说明 |
|---|---|
boolean add(E e) |
添加单个元素(有序插入,重复元素返回 false) |
boolean addAll(Collection<? extends E> c) |
添加指定集合的所有元素(自动去重 + 排序) |
2. 查询操作(有序专属核心)
| 方法 | 功能说明 |
|---|---|
E first() |
返回集合中最小元素(按排序规则) |
E last() |
返回集合中最大元素(按排序规则) |
E ceiling(E e) |
返回大于等于 e 的最小元素(无则返回 null) |
E floor(E e) |
返回小于等于 e 的最大元素(无则返回 null) |
E higher(E e) |
返回严格大于 e 的最小元素(无则返回 null,区别于 ceiling) |
E lower(E e) |
返回严格小于 e 的最大元素(无则返回 null,区别于 floor) |
SortedSet<E> subSet(E fromElement, E toElement) |
返回子集:包含 fromElement,不包含 toElement(遵循原排序规则) |
SortedSet<E> headSet(E toElement) |
返回头部子集:所有小于 toElement 的元素 |
SortedSet<E> tailSet(E fromElement) |
返回尾部子集:所有大于等于 fromElement 的元素 |
3. 删除操作
| 方法 | 功能说明 |
|---|---|
boolean remove(Object o) |
删除指定元素(成功返回 true,失败返回 false) |
E pollFirst() |
删除并返回最小元素(集合为空返回 null) |
E pollLast() |
删除并返回最大元素(集合为空返回 null) |
void clear() |
清空集合所有元素 |
4. 遍历操作
支持 3 种常用遍历方式:迭代器、增强 for 循环、forEach (Java 8+)
示例:常用 API 综合使用
import java.util.TreeSet;
public class TreeSetAPIDemo {
public static void main(String[] args) {
TreeSet<Integer> treeSet = new TreeSet<>();
// 1. 新增元素
treeSet.add(3);
treeSet.add(1);
treeSet.add(5);
treeSet.add(2);
treeSet.add(4);
System.out.println("初始化集合:" + treeSet); // [1, 2, 3, 4, 5]
// 2. 查询操作
System.out.println("最小元素:" + treeSet.first()); // 1
System.out.println("最大元素:" + treeSet.last()); // 5
System.out.println(">=3 的最小元素:" + treeSet.ceiling(3)); // 3
System.out.println("<=3 的最大元素:" + treeSet.floor(3)); // 3
System.out.println(">3 的最小元素:" + treeSet.higher(3)); // 4
System.out.println("<3 的最大元素:" + treeSet.lower(3)); // 2
System.out.println("子集 [2,4):" + treeSet.subSet(2, 4)); // [2, 3]
System.out.println("头部子集 <4:" + treeSet.headSet(4)); // [1, 2, 3]
System.out.println("尾部子集 >=3:" + treeSet.tailSet(3)); // [3, 4, 5]
// 3. 删除操作
treeSet.remove(3); // 删除元素3
System.out.println("删除3后:" + treeSet); // [1, 2, 4, 5]
System.out.println("删除最小元素:" + treeSet.pollFirst()); // 1
System.out.println("删除最大元素:" + treeSet.pollLast()); // 5
System.out.println("删除首尾后:" + treeSet); // [2, 4]
// 4. 遍历操作
System.out.println("增强 for 遍历:");
for (Integer num : treeSet) {
System.out.print(num + " "); // 2 4
}
System.out.println("\nforEach 遍历:");
treeSet.forEach(num -> System.out.print(num + " ")); // 2 4
}
}
六、关键注意事项(避坑指南)
- 排序规则一致性
- 自定义类的排序规则(
compareTo/compare方法)应与equals()方法保持一致(即排序返回 0 时,equals()应返回true),否则会出现「元素去重但equals判断不相等」的逻辑矛盾。 - 若存入
TreeSet的自定义对象修改了影响排序的字段 (如 User 的 age 字段),会导致元素在红黑树中的位置失效,无法正确查找、删除该元素(推荐将排序字段设为final,禁止修改)。
- 元素类型限制
- 自然排序下,元素必须实现
Comparable接口,否则添加元素时会抛出ClassCastException。 - 不能添加不同类型的元素(如同时添加
Integer和String),否则会抛出ClassCastException(无法比较不同类型)。
- null 元素限制
-
自然排序:不支持
null元素(compareTo方法会抛出NullPointerException)。 -
定制排序:可手动支持
null(需在compare方法中先判断null,避免空指针),示例:TreeSet<String> set = new TreeSet<>((o1, o2) -> { if (o1 == null) return -1; // null 排最前面 if (o2 == null) return 1; return o1.compareTo(o2); }); set.add(null); set.add("Java"); System.out.println(set); // [null, Java]
- 线程安全问题
-
TreeSet是非线程安全的,多线程并发修改(如add+remove)会导致数据不一致。 -
解决方案:使用
Collections.synchronizedSortedSet包装TreeSet,示例:import java.util.Collections; import java.util.SortedSet; import java.util.TreeSet; public class TreeSetThreadSafeDemo { public static void main(String[] args) { // 线程安全的 TreeSet SortedSet<Integer> safeTreeSet = Collections.synchronizedSortedSet(new TreeSet<>()); safeTreeSet.add(1); safeTreeSet.add(2); } }
- 与 HashSet 的核心区别| 特性 | TreeSet | HashSet ||---------------------|------------------------|------------------------|| 底层实现 | TreeMap(红黑树) | HashMap(哈希表) || 有序性 | 有序(自然 / 定制排序) | 无序(插入顺序不保证) || 唯一性判断 | 排序规则(compareTo/compare) | equals () + hashCode () || 时间复杂度 | 增删查 O (log n) | 增删查 O (1)(哈希无冲突) || null 支持 | 自然排序不支持,定制排序可支持 | 支持(仅允许一个 null) || 适用场景 | 需要有序去重的场景 | 无需有序,追求高性能去重 |
七、总结
- 核心特性:有序(自然 / 定制排序)、无重复、基于红黑树、O (log n) 时间复杂度、非线程安全、不默认支持 null。
- 排序规则 :自然排序(元素实现
Comparable)、定制排序(传入Comparator),重复元素判断依赖排序规则返回 0。 - 核心 API :
first()/last()(首尾元素)、ceiling()/floor()(边界元素)、pollFirst()/pollLast()(删除首尾)、subSet()(子集)是有序场景的核心操作。 - 避坑要点 :排序字段不可修改、排序规则与
equals一致、线程安全需包装、避免添加不同类型元素。 - 适用场景:排行榜(按分数排序)、有序字典(按关键字排序)、范围查询(如查询 [10, 20] 之间的元素)等需要有序去重的场景。