【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 及其对应实现类源码。
浙公网安备 33010602011771号