Set 集合
Set 集合是 Java 集合框架中非常重要的一员!✨ 它与 List 的最大区别在于:Set 中的元素是唯一的,不允许重复。这个特性让 Set 在特定场景下非常有用!
什么是 Set 集合?🔍
Set 是一个不允许包含重复元素的集合接口,它继承自 Collection
接口。Set 集合中的元素是唯一的,没有重复值。
Set 的主要特点包括:
- ✅ 元素唯一性 - 不会存储重复的元素
- 🔁 无序性(大部分实现) - 不保证元素的插入顺序
- 🚫 不允许通过索引访问 - 因为没有固定的顺序
📝 注意:虽然 Set 通常是无序的,但某些实现(如 LinkedHashSet)会保持插入顺序。
为什么要使用 Set 集合?🎯
Set 集合在以下场景中特别有用:
- 去重处理 🧹 - 自动去除重复数据,无需手动判断
- 成员关系检查 🔍 - 快速判断某个元素是否存在
- 数学集合运算 ➕➖✖️ - 轻松实现交集、并集、差集等操作
- 数据唯一性约束 ⚠️ - 确保数据集中不会出现重复项
例如:存储唯一用户名、统计词汇量、过滤重复数据、权限集合等场景。
常见的 Set 集合实现类 📦
Java 提供了多个 Set 接口的实现类,各有特点:
实现类 | 特点 | 底层实现 | 适用场景 |
---|---|---|---|
HashSet 🚀 | 最常用,查询最快 | 基于 HashMap | 需要快速查找且不关心顺序 |
LinkedHashSet 🔗 | 保持插入顺序 | 哈希表+双向链表 | 需要去重且保持插入顺序 |
TreeSet 🌳 | 自动排序 | 红黑树 | 需要元素有序且去重 |
EnumSet 🎭 | 专为枚举优化 | 位向量 | 存储枚举值,性能极高 |
💡 提示:在大多数场景中,
HashSet
是最常用的 Set 实现。
Set 集合常用方法 🛠️
1. 添加元素
Set<String> set = new HashSet<>();
set.add("Apple"); // 添加元素 ✅
set.add("Apple"); // 添加重复元素,无效 ❌
2. 删除元素
set.remove("Apple"); // 删除指定元素
set.clear(); // 清空所有元素
3. 检查元素是否存在
boolean exists = set.contains("Apple"); // 检查是否包含元素
4. 获取集合大小
int size = set.size(); // 返回元素个数
5. 判断集合是否为空
boolean isEmpty = set.isEmpty();
6. 遍历 Set(多种方式)
// 增强 for 循环
for (String fruit : set) {
System.out.println(fruit);
}
// 使用迭代器
Iterator<String> it = set.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
// Java 8+ forEach + lambda
set.forEach(fruit -> System.out.println(fruit));
set.forEach(System.out::println); // 方法引用
7. 集合运算 ✨
Set<String> set1 = new HashSet<>(Arrays.asList("A", "B", "C"));
Set<String> set2 = new HashSet<>(Arrays.asList("B", "C", "D"));
// 并集
Set<String> union = new HashSet<>(set1);
union.addAll(set2); // [A, B, C, D]
// 交集
Set<String> intersection = new HashSet<>(set1);
intersection.retainAll(set2); // [B, C]
// 差集
Set<String> difference = new HashSet<>(set1);
difference.removeAll(set2); // [A]
8. 转换为数组
String[] array = set.toArray(new String[0]);
💎 总结一下:Set 的主要特点就是去重和快速查找!当你需要存储不重复的元素集合时,Set 是最佳选择!
Set 集合的实现原理 🛠️
Set 集合的实现原理因具体实现而异,但通常情况下,Set 集合的实现原理都基于哈希表(HashTable)或哈希映射(HashMap)。
- 哈希表(HashTable):哈希表是一种数据结构,用于存储键值对。它使用哈希函数将键映射到一个索引,然后存储在数组中的指定位置。
- 哈希映射(HashMap):HashMap 是 Java 中最常用的 Set 实现,它基于哈希表。它将键映射到一个索引,然后存储在数组中的指定位置。
- 红黑树(TreeSet):TreeSet 是基于红黑树的数据结构,它允许元素有序存储。
- 位向量(EnumSet):EnumSet 是 Java 枚举类型的特殊实现,它使用位向量来存储枚举值。
- 其他实现:还有其他一些实现,如 LinkedHashSet、SortedSet 等,它们也基于哈希表或红黑树。
📝 注意:Set 集合的实现原理因具体实现而异,但通常情况下,Set 集合的实现原理都基于哈希表或哈希映射。
Set 集合常见面试问题 🤔
🔍 基础概念类问题
1. Set 和 List 的主要区别是什么?
答:
特性 | Set | List |
---|---|---|
元素唯一性 | ✅ 不允许重复 | ❌ 允许重复 |
顺序保证 | 大部分无序(除LinkedHashSet, TreeSet) | ✅ 保持插入顺序 |
索引访问 | ❌ 不支持 | ✅ 支持按索引访问 |
实现类 | HashSet, TreeSet, LinkedHashSet | ArrayList, LinkedList, Vector |
2. 为什么 Set 不允许重复元素?
答: Set 的"唯一性"是通过元素的 hashCode()
和 equals()
方法实现的。当添加新元素时:
- 先计算哈希值定位存储位置
- 如果该位置有元素,调用
equals()
比较 - 如果返回 true,视为相同元素,拒绝添加
// 示例:自定义对象需要重写 hashCode() 和 equals()
class Student {
private String name;
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public boolean equals(Object obj) {
// 实现相等性逻辑
}
}
⚖️ 实现类对比类问题
3. HashSet、LinkedHashSet 和 TreeSet 的区别?
答:
特性 | HashSet | LinkedHashSet | TreeSet |
---|---|---|---|
底层实现 | HashMap | LinkedHashMap | 红黑树 |
性能 | 查询最快 O(1) | 稍慢于 HashSet | 查询 O(log n) |
顺序 | 无序 | 插入顺序 | 自然排序/定制排序 |
线程安全 | 否 | 否 | 否 |
null值 | 允许1个null | 允许1个null | 不允许null |
4. 什么情况下选择哪种 Set 实现?
答:
- HashSet:需要最高性能,不关心顺序 ✅
- LinkedHashSet:需要保持插入顺序 📝
- TreeSet:需要元素自动排序 🔄
- CopyOnWriteArraySet:需要线程安全 🛡️
⚙️ 底层原理类问题
5. HashSet 的底层实现原理是什么?
答: HashSet 实际上是通过 HashMap 实现的:
// HashSet 的源码实现
public class HashSet<E> {
private transient HashMap<E, Object> map;
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT) == null; // 值固定为 PRESENT
}
}
所有元素作为 HashMap 的 key 存储,value 是一个固定的 Object 对象。
6. TreeSet 如何保证元素有序?
答: TreeSet 通过两种方式排序:
- 自然排序:元素实现
Comparable
接口 - 定制排序:创建 TreeSet 时传入
Comparator
// 自然排序
Set<String> set = new TreeSet<>(); // 按字符串自然顺序
// 定制排序
Set<Integer> set = new TreeSet<>((a, b) -> b - a); // 降序排列
🛠️ 实战应用类问题
7. 如何用 Set 实现去重?
答:
List<String> listWithDuplicates = Arrays.asList("A", "B", "A", "C");
Set<String> set = new HashSet<>(listWithDuplicates); // 自动去重
System.out.println(set); // 输出 [A, B, C]
8. 如何在 Set 中存储自定义对象?
答: 必须重写 hashCode()
和 equals()
方法:
class User {
private String name;
private int age;
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof User)) return false;
User other = (User) obj;
return age == other.age && Objects.equals(name, other.name);
}
}
🚀 高级特性类问题
9. Set 是线程安全的吗?如何实现线程安全?
答: 标准 Set 实现都不是线程安全的。实现线程安全的方法:
// 方法1:使用 Collections.synchronizedSet()
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
// 方法2:使用 CopyOnWriteArraySet(读多写少场景)
Set<String> safeSet = new CopyOnWriteArraySet<>();
// 方法3:使用 ConcurrentHashMap.keySet()
Set<String> concurrentSet = ConcurrentHashMap.newKeySet();
10. 如何实现两个 Set 的集合运算?
答:
Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3));
Set<Integer> set2 = new HashSet<>(Arrays.asList(3, 4, 5));
// 并集
Set<Integer> union = new HashSet<>(set1);
union.addAll(set2); // [1, 2, 3, 4, 5]
// 交集
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2); // [3]
// 差集
Set<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2); // [1, 2]