【Java集合框架】3 - 8 TreeSet 集合
§3-8 TreeSet
集合
3-8.1 TreeSet
:可排序的集合
TreeSet
本身并不具备特殊的方法,因此一般而言,直接调用 Collection
中的方法。
TreeSet
在底层使用红黑树存储数据,其增删改查性能都较好,不同于 HashSet
和 LinkedHashSet
,该集合可排序,不重复,无索引。
默认排序规则:
- 对于数值类型
Ineger
,Double
,默认按照从小到大升序排序; - 对于字符、字符串类型,按照字符在 ASCII 码表中的编码升序排序;
自定义类可排序的实现有两种方式:
- Java Bean 类实现
Comparable
并重写compareTo
方法; - 构造器中传入
Comparator
实现类对象,其中重写compare
方法。
但无论是哪种方式,最终调用的方法所依赖的逻辑相同:将待添加的对象(this
)与树中的某一结点(T o
)做比较,计算所得返回值若小于零,则添加在该结点的左子结点处;若返回值大于零,则添加在右子结点处;若为零,则相同,舍弃该对象(若添加为根节点,则例外)。
这一特性使得类不需要重写 equals
和 hashCode
方法。
方式一 - 实现 Comparable
接口:现有下述 Java Bean 类,并实现 Comparable
接口
public class Student implements Comparable<Student> {
private String name;
private int age;
/*
此处略去空参、全参构造器、Getter & Setter、toString 方法
*/
@Override
public int compareTo(Student o) {
//按姓名(不含中文)排序:逐字按照 ASCII 编码顺序排序
return this.getName().compareTo(o.getName());
//或者按年龄升序排列
//return this.getAge() - o.getAge();
}
}
在测试类中,创建 TreeSet
对象,并添加以下对象:
public class Test {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
Student s1 = new Student("Zhang San", 23);
Student s2 = new Student("Li Si", 24);
Student s3 = new Student("Wang Wu", 25);
Student s4 = new Student("Zhao Liu", 26);
Student s5 = new Student("Zhang San", 23);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
for (Student t : ts) {
System.out.println(t);
}
}
}
运行,得到
Student{name = Li Si, age = 24}
Student{name = Wang Wu, age = 25}
Student{name = Zhang San, age = 23}
Student{name = Zhao Liu, age = 26}
添加的结点按照姓名的 ASCII 编码顺序排序,添加过程同红黑树的节点添加过程,通过不断地调用 compareTo
方法判断添加为左/右结点。
在本例中,compareTo
实际上调用了 String
中重写过的 compareTo
方法比较两个字符串。
方式二 - 构造器中传入 Comparator
实现类对象:这种情况适用于 Compare
接口中的 compareTo
方法无法满足条件时使用(这种情况多是因为该类已经实现 Compare
接口并重写了 compareTo
方法),这时可以往构造器中传入 Comparator
实现类对象(可使用 Lambda 表达式)。若两种方式同时存在,则优先以方式二的定义进行比较判断。
以字符串为例,现自定义排序规则,使得字符串优先以长度升序排序,若长度一致,则按字符升序排序
public class Test {
public static void main(String[] args) {
//传入 Comparator 实现类对象(Lambda)
TreeSet<String> tsSt = new TreeSet<>(((o1, o2) -> {
//先按照字符串的长度升序排序
int i = o1.length() - o2.length();
//若长度相同,则按字符升序排序(忽略大小写)
i = i == 0 ? o1.compareToIgnoreCase(o2) : i;
return i;
}));
tsSt.add("Zhang San");
tsSt.add("Zhang Ban");
tsSt.add("Li si");
tsSt.add("Wang Wu");
for (String s : tsSt) {
System.out.println(s);
}
}
}
运行,得到:
Li si
Wang Wu
Zhang Ban
Zhang San
3-8.2 两种排序方式的选择
两种排序方式背后所执行的逻辑是完全相同的,一般情况下,我们会默认选择第一种方式,即让 Java Bean 类实现 Comparable
接口并重写 compareTo
方法,作自然排序/默认排序。
当 compareTo
方法的实现不能够满足需求时,选择第二种方式,在构造器中传入 Comparator
接口并重写 compare
方法。
一般而言,Java 中提供的类(例如 Integer
和 String
)都已经实现 Comparable
,因此无需考虑对象是否可比。真正应当考虑可比性的是自定义的 Java Bean 类。
示例:使用 TreeSet
对整数自然排序
TreeSet<Integer> ts = new TreeSet<>();
ts.add(5);
ts.add(3);
ts.add(1);
ts.add(2);
ts.add(4);
System.out.println(ts);
得到:
[1, 2, 3, 4, 5]
3-8.3 Set
集合使用场景与源码
- 若想要集合中元素可重复,则使用
ArrayList
(使用最多,基于数组); - 若想要集合中元素可重复,且增删操作较查找更频繁,则使用
LinkedList
(基于链表); - 若想要集合中元素无重复,则使用
HashSet
(使用最多,基于哈希表); - 若想要集合中元素无重复,且保证存取有序性,则使用
LinkedHashSet
(基于哈希表和双链表,效率低于HashSet
); - 若想要集合中元素无重复,且数据可排序,则使用
TreeSet
(基于红黑树,后续也可使用List
集合实现排序);
所有 Set
集合 HashSet
, LinkedHashSet
和 TreeSet
在底层,实际上创建的是 HashMap
, LinkedHashMap
和 TreeMap
对象,这些都是 Map
接口的实现类。因此,想要分析上述 Set
实现类源码,应当先了解 Map
及其对应实现类源码。