Set是单列集合中另一个大的门派;它表示一个不允许重复元素且不保证顺序的集合;
以下是关于 Set
的详细语法和使用说明:
一、Set 的核心特性
- 唯一性:不允许重复元素(必须重写
equals()
和hashCode()
方法,因为数据的存储,获取和判断是否一样都依赖这俩方法,不过ideal自动生成的重写足够用)。 - 无序性:默认不保证元素的存储顺序(具体取决于实现类)。
- 允许 null 元素:大多数实现类允许一个
null
值(但TreeSet
不允许)。
二、常用实现类
类名 | 描述 |
| 基于哈希表实现,无序,查询效率高(O(1)),允许一个 |
| 基于哈希表和链表,维护插入顺序,查询效率略低于 |
| 基于红黑树实现元素按自然顺序或自定义 |
一定要了解哈希表的相关,这个并不难,课下可搜集一大堆资料,主要是这玩意整理成文字远远不如看视频来得简单;
哈希表的原理大概就是通过hashCode()
方法算出元素的应该存放到哈希表的哪个位置(所以以哈希表为底层数据结构的集合类的查询效率也很高,因为他是直接算出位置的),如果哈希值相同就调用equals()判断,如果俩都一样就判定元素相同,至于“哈希碰撞”其实就是俩不同的哈希值一样;
如果是基于各种树的集合类,基本就是,数据是排序存储的;
三、基础语法与操作
1. 初始化 Set
// 使用实现类初始化(推荐指定泛型类型)
Set<String> hashSet = new HashSet<>();
Set<Integer> linkedHashSet = new LinkedHashSet<>();
Set<Double> treeSet = new TreeSet<>();
2. 常用方法
Set<String> set = new HashSet<>();
// 添加元素
set.add("Apple"); // 成功返回 true,重复元素返回 false
set.addAll(Arrays.asList("Banana", "Cherry")); // 批量添加
// 删除元素
set.remove("Apple"); // 存在则删除并返回 true
set.clear(); // 清空集合
// 查询操作
boolean exists = set.contains("Banana"); // 检查是否存在
int size = set.size(); // 获取元素数量
boolean isEmpty = set.isEmpty(); // 判断是否为空
// 遍历集合
for (String element : set) {
System.out.println(element);
}
// 使用迭代器
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//lambda表达式
set.forEach(element -> System.out.println(element));
三、HashSet
相关
1、HashSet 的核心特性
-
- 唯一性:不允许重复元素(依赖
equals()
和hashCode()
方法判断重复)。 - 无序性:不保证元素的存储或遍历顺序(基于哈希表实现)。
- 允许
null
元素:支持存储一个null
值。 - 非线程安全:多线程环境下需手动同步或使用并发容器。
- 高性能:插入、删除、查询操作平均时间复杂度为 O(1)。
- 去重是HashCode天然的能力,就凭这个就可以使用在很多地方;
- 唯一性:不允许重复元素(依赖
注意:在HashSet
集合中,数据在存储或者验证唯一性时,很重要的关键是“哈希值”;因此,处于原理考虑,如果HashSet
集合中存储的元素是对象类型,就一定要重写hashCode()和equals()方法;因为HashSet
底层的实现逻辑是依靠哈希表数据结构,无论是处于判断数据添加时的存储位置还是判断两个元素是否相等,重写这俩方法都异常的重要;通常是俩方法都相等才算相等;
2、初始化 HashSet
2.1. 基础初始化
// 默认构造:初始容量16,加载因子0.75
Set<String> set1 = new HashSet<>();
// 指定初始容量(减少扩容次数)
Set<Integer> set2 = new HashSet<>(32);
// 指定初始容量和加载因子(默认0.75)
Set<Double> set3 = new HashSet<>(64, 0.5f);
// 通过已有集合初始化
List<String> list = Arrays.asList("A", "B", "C");
Set<String> set4 = new HashSet<>(list);
HashCode底层原理利用的是哈希表,因此,对于HashCode的初始化基本都是对哈希表的初始化;
初始容量:哈希表的总容量
加载因子:当哈希表的位置被占75%后,哈希表扩容两倍
3、常用方法
3.1. 元素操作
HashSet<String> set = new HashSet<>();
// 添加元素
set.add("Apple"); // 成功返回true,重复返回false
set.addAll(List.of("Banana", "Cherry")); // 批量添加
// 删除元素
set.remove("Apple"); // 存在则删除并返回true
set.removeAll(List.of("Banana")); // 批量删除
set.clear(); // 清空集合
// 检查元素
boolean exists = set.contains("Cherry"); // true
boolean isEmpty = set.isEmpty(); // false
int size = set.size(); // 当前元素数量
3.2. 遍历方式
// for-each 循环
for (String element : set) {
System.out.println(element);
}
// 迭代器
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// Java 8+ Stream API
set.stream().forEach(System.out::println);
四、LinkedHashSet
相关
作为HashSet
的子类功能基本继承HashSet
,唯一不同的是底层的实现逻辑在哈希表的基础上添加了双向链表;使得LinkedHashSet
支持有序,即读取的顺序和存储的顺序一致的特性;
五、TreeSet
相关
其实关于Set接口下面的几个实现类的方法都差不多,现在主要的问题就是他们各自的特点,因此如何使用不会着重讲,主要是他们的特性和不同;而TreeSet
最大的特点就是,凡事他的元素都会自动排序;
注意:TreeSet的底层是红黑树,因此不需要重写HashCode()和equals()方法;
但作为Set集合的一员,他们家族的老本行---“唯一性”,TreeSet是肯定存在的,那他是如何做到的呢?
主要依赖于元素的比较逻辑(compareTo
或compare
方法)而非equals
方法,如果是自定义的类型没有重写compareTo
方法就会报错;
1、TreeSet 的核心特性
-
-
- 有序性:元素按自然顺序(Comparable)或自定义规则(Comparator)自动排序;
- 唯一性:不允许重复元素(依赖 equals() 和 compareTo()/compare() 方法);
- 禁止 null 元素:尝试添加 null 会抛出 NullPointerException;
- 基于红黑树:底层通过平衡二叉搜索树实现,操作时间复杂度为 O(log n);
- 非线程安全:需手动同步或使用并发容器(如 ConcurrentSkipListSet)。
-
2、常用方法与操作
2.1. 基本操作
TreeSet<String> set = new TreeSet<>();
// 添加元素
set.add("Apple"); // 成功返回 true
set.addAll(List.of("Banana", "Cherry"));
// 删除元素
set.remove("Apple"); // 存在则返回 true
set.pollFirst(); // 删除并返回第一个元素
set.pollLast(); // 删除并返回最后一个元素
2.2. 查询操作
// 获取极值
String first = set.first(); // 第一个元素
String last = set.last(); // 最后一个元素
// 范围查询
String lower = set.lower("Banana"); // 小于指定元素的最大元素
String higher = set.higher("Banana"); // 大于指定元素的最小元素
// 检查元素
boolean contains = set.contains("Cherry");
int size = set.size();
boolean isEmpty = set.isEmpty();
2.3. 子集操作
// 获取 [fromElement, toElement) 范围的子集
SortedSet<String> subset = set.subSet("Banana", "Cherry");
// 获取小于指定元素的子集
SortedSet<String> headSet = set.headSet("Cherry");
// 获取大于等于指定元素的子集
SortedSet<String> tailSet = set.tailSet("Banana");
2.4、遍历方式
// 自然顺序遍历
for (String element : set) {
System.out.println(element);
}
// 降序遍历(通过 descendingIterator)
Iterator<String> descIterator = set.descendingIterator();
while (descIterator.hasNext()) {
System.out.println(descIterator.next());
}
// Java 8+ Stream API
set.stream().forEach(System.out::println);
3、排序操作(以下代码请多注意注释内容,注释内容解释了大部分原理,看似复杂其实还行)
3.1. 自然排序(如果是自定义类需实现 Comparable
接口)
// 默认自然排序(如 Integer、String 已实现 Comparable)
TreeSet<Integer> numbers = new TreeSet<>();
numbers.add(5);
numbers.add(1);
System.out.println(numbers); // 输出 [1, 5]
对于自定义的类,需要自己去书写一定的规则;
// 自定义对象需实现 Comparable
class Student implements Comparable<Student> {
String name;
int age;
@Override
public int compareTo(Student o) {
return this.getAge() - o.getAge();
}
// this: 表示当前要添加的元素
// o: 表示已经在红黑树存在的元素
// 返回值:
// 负数: 认为要添加的元素是小的,存右边
// 正数: 认为要添加的元素是大的,存左边
// 0: 认为要添加的元素已经存在,舍弃
}
TreeSet<Student> students = new TreeSet<>();
3.2. 自定义排序(通过 Comparator
)
默认情况下第一种排序已经够用了,第二种其实就是在初始化TressSet时传入一个规则,使得这个规则可以覆盖原来的规则,在不改变原类型源码的情况下,更灵活适应各种情况;
形式不重要,仔细品味,其实就是把第一种重写的compareTo给单独拎了出来而已;
//o1表示当前要添加的元素
//o2表示已经在红黑树存在的元素
//返回值规则跟之前是一样的
TreeSet<String> ts = new TreeSet<>((o1, o2) -> {
// 按照长度排序
int i = o1.length() - o2.length();
// 如果一样长则按照首字母排序
i = i == 0 ? o1.compareTo(o2) : i;
return i;
});
reverseSet.add("Apple");
reverseSet.add("Banana");
System.out.println(reverseSet); // 输出 [Banana, Apple]
// 使用 Comparator 对象
Comparator<Student> ageComparator = Comparator.comparingInt(s -> s.age);
TreeSet<Student> studentsByAge = new TreeSet<>(ageComparator);
六、特殊操作与注意事项
一定要注意,虽然都是Set系列下的集合,但是他们实现唯一性和存储的方式不完全一样,一定要注意区分,这是重点;
1. 元素唯一性实现原理【HashSet和TreeHashSet集合的原理】
- 当向
Set
添加对象时,会调用对象的hashCode()
方法计算哈希值。 - 如果哈希值冲突,再通过
equals()
方法比较内容是否相同。 - 重要:自定义对象作为元素时,必须重写
hashCode()
和equals()
方法!
2. 排序与比较【TreeSet
集合实现唯一性和存储的原理】
- 自然排序:
TreeSet
要求元素实现Comparable
接口。
Set<Integer> sortedSet = new TreeSet<>(Arrays.asList(3, 1, 2)); // 自动排序为 [1, 2, 3]
- 自定义排序:通过
Comparator
指定排序规则。
Set<String> customSorted = new TreeSet<>((a, b) -> b.compareTo(a)); // 降序排列