7.5 Set接口
7.5.1 Set接口简介
Set接口是Java集合框架中的一个重要组成部分,它继承自Collection接口。Set接口的主要特点是它的元素是无序的,并且保证元素的唯一性,即不允许重复。这与List接口相比,List允许重复元素并且元素是有序的,形成了鲜明的对比。
Set接口的实现类
Set接口有几个常见的实现类:
- HashSet:最常用的一种Set实现,它使用哈希表(实际上是一个HashMap实例)来存储元素。HashSet提供了很好的查找、获取和存储性能。
- LinkedHashSet:类似于HashSet,但它使用链表维护元素的插入顺序,因此迭代访问Set时,元素的返回顺序是其插入顺序。
- TreeSet:基于红黑树(一种自平衡的二叉查找树)实现,元素会按照自然排序(Natural Ordering)或创建Set时提供的Comparator进行排序。
7.5.2 HashSet
HashSet是Set接口的一个基本实现类,它确保集合中的元素不重复,主要通过以下步骤实现:
工作原理和用例
- 存储元素:当向HashSet中添加元素时,首先调用元素的
hashCode()
方法来计算元素的哈希码,这个哈希码决定了元素在HashSet中的存储位置。 - 检查重复:如果计算得到的位置已经有元素存在,HashSet会调用
equals()
方法来检查两个元素是否相同。如果equals()
返回true
,新元素不会被添加到集合中。
示例代码
import java.util.HashSet;
import java.util.Iterator;
public class Example07 {
public static void main(String[] args) {
HashSet<String> hset = new HashSet<>();
hset.add("张三");
hset.add("李四");
hset.add("王五");
hset.add("李四"); // 尝试添加重复元素
Iterator<String> it = hset.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
运行结果和解释
在上述示例中,即便我们尝试将"李四"添加两次,输出结果也只会显示一次"李四"。这证明了HashSet不允许重复元素。输出顺序也可能与添加顺序不同,因为HashSet不保证元素的顺序。
注意事项
- 自定义对象:如果在HashSet中使用自定义对象,你需要重写这个对象的
hashCode()
和equals()
方法,以确保HashSet能正确地识别重复元素。 - 性能考虑:虽然HashSet在添加和查找元素时非常高效,但这种效率依赖于
hashCode()
方法的实现。一个糟糕的hashCode()
实现会导致频繁的哈希冲突,从而降低性能。
HashSet是Java集合中广泛使用的一个组件,了解其工作原理对于实现基于性能的高效Java应用至关重要。
7.5.3 LinkedHashSet
介绍
在Java中,LinkedHashSet
是HashSet
的一个子类,它结合了哈希表的快速访问特性和链表的顺序性。LinkedHashSet
内部通过双向链表来维护元素的插入顺序,因此迭代访问LinkedHashSet
时,元素的返回顺序与它们的插入顺序相同。
特点
- 有序性:与
HashSet
不同,LinkedHashSet
维护着一个运行于所有条目的双向链表。这使得迭代顺序是可预测的,即元素以它们被插入到集合中的顺序来迭代。 - 性能:
LinkedHashSet
在添加和删除元素时保持与HashSet
相同的性能特征,即平均时间复杂度为O(1)。但是由于维护链表的开销,它的性能可能略低于HashSet
。 - 内存使用:由于
LinkedHashSet
同时维护哈希表结构和链表结构,因此它比HashSet
消耗更多的内存。
使用场景
LinkedHashSet
适用于需要维护插入顺序的场景,例如缓存、先进先出(FIFO)的记录和记忆最近使用的项。
示例
以下是一个简单的LinkedHashSet
应用示例,展示了如何创建和迭代这种类型的集合。
示例代码
import java.util.Iterator;
import java.util.LinkedHashSet;
public class Example10 {
public static void main(String[] args) {
LinkedHashSet<String> set = new LinkedHashSet<>();
// 向集合中添加字符串
set.add("张三");
set.add("李四");
set.add("王五");
// 获取Iterator对象
Iterator<String> it = set.iterator();
// 通过while循环判断集合中是否有元素
while (it.hasNext()) {
String obj = it.next();
// 调用Iterator的next()方法获取元素
System.out.println(obj);
}
}
}
运行结果
该程序的输出将严格按照元素的添加顺序:
张三
李四
王五
结论
LinkedHashSet
提供了一种强大的机制,用于保持集合中元素的插入顺序,同时还保持了哈希表的查询效率。这使得它在需要顺序访问时成为一个优于HashSet
的选择。如果应用程序不需要维护插入顺序,那么使用HashSet
可能更合适,因为它在内存使用上更为高效。
7.5.4 TreeSet
概述
TreeSet
是Java集合框架中的一个实现Set
接口的类,它使用红黑树(一种自平衡的二叉查找树)来存储元素。由于这种结构的特性,TreeSet
能确保集合元素的排序以及高效的访问和查找性能。这使得TreeSet
成为在需要排序集合时的理想选择。
特点
- 元素唯一性: 如同其他
Set
实现,TreeSet
不允许重复元素。 - 自动排序:
TreeSet
中的元素在添加时会自动按照自然排序(即元素的自然顺序)或者根据构造器提供的Comparator
进行排序。 - 非同步:
TreeSet
是非同步的,如果多个线程同时访问一个TreeSet
,并且至少有一个线程修改了集合,它必须保持外部同步。 - 性能: 对于常见操作如添加、删除和查找元素,
TreeSet
提供了优秀的性能,时间复杂度为O(log n)。
使用场景
TreeSet
适用于需要大量动态插入和需要有序访问集合的场合。例如,在一个排序的列表中维护无重复的错误代码或者用户ID等。
示例
以下是使用TreeSet
的一个简单示例,演示如何添加元素以及如何利用其自动排序的特性。
示例代码
import java.util.TreeSet;
public class Example11 {
public static void main(String[] args) {
TreeSet<Integer> ts = new TreeSet<>();
// 向集合中添加元素
ts.add(29);
ts.add(3);
ts.add(101);
ts.add(21);
System.out.println("创建的TreeSet集合为:" + ts);
// 获取并输出集合的首尾元素
System.out.println("TreeSet集合首元素为:" + ts.first());
System.out.println("TreeSet集合尾部元素为:" + ts.last());
// 获取特定条件的元素
System.out.println("集合中小于或等于9的最大的元素为:" + ts.floor(9));
System.out.println("集合中大于10的最小的元素为:" + ts.higher(10));
System.out.println("集合中大于100的最小的元素为:" + ts.higher(100));
// 删除并输出第一个元素
Object first = ts.pollFirst();
System.out.println("删除的第一个元素为:" + first);
System.out.println("删除第一个元素后TreeSet集合变为:" + ts);
}
}
运行结果
输出将展示集合元素的自动排序,以及首尾元素的访问和条件查询的结果。例如:
创建的TreeSet集合为:[3, 21, 29, 101]
TreeSet集合首元素为:3
TreeSet集合尾部元素为:101
集合中小于或等于9的最大的元素为:3
集合中大于10的最小的元素为:21
集合中大于100的最小的元素为:101
删除的第一个元素为:3
删除第一个元素后TreeSet集合变为:[21, 29, 101]
总结
TreeSet
是一个强大的集合类,适用于需要排序和高效访问的场景。它通过红黑树确保元素的顺序和快速访问,适合作为数据结构的一部分用于排序的需求。