跳到主要内容

Set 集合

Set 集合是 Java 集合框架中非常重要的一员!✨ 它与 List 的最大区别在于:Set 中的元素是唯一的,不允许重复。这个特性让 Set 在特定场景下非常有用!

什么是 Set 集合?🔍

Set 是一个不允许包含重复元素的集合接口,它继承自 Collection 接口。Set 集合中的元素是唯一的,没有重复值。

Set 的主要特点包括:

  • 元素唯一性 - 不会存储重复的元素
  • 🔁 无序性(大部分实现) - 不保证元素的插入顺序
  • 🚫 不允许通过索引访问 - 因为没有固定的顺序

📝 注意:虽然 Set 通常是无序的,但某些实现(如 LinkedHashSet)会保持插入顺序。

为什么要使用 Set 集合?🎯

Set 集合在以下场景中特别有用:

  1. 去重处理 🧹 - 自动去除重复数据,无需手动判断
  2. 成员关系检查 🔍 - 快速判断某个元素是否存在
  3. 数学集合运算 ➕➖✖️ - 轻松实现交集、并集、差集等操作
  4. 数据唯一性约束 ⚠️ - 确保数据集中不会出现重复项

例如:存储唯一用户名、统计词汇量、过滤重复数据、权限集合等场景。

常见的 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)。

  1. 哈希表(HashTable):哈希表是一种数据结构,用于存储键值对。它使用哈希函数将键映射到一个索引,然后存储在数组中的指定位置。
  2. 哈希映射(HashMap):HashMap 是 Java 中最常用的 Set 实现,它基于哈希表。它将键映射到一个索引,然后存储在数组中的指定位置。
  3. 红黑树(TreeSet):TreeSet 是基于红黑树的数据结构,它允许元素有序存储。
  4. 位向量(EnumSet):EnumSet 是 Java 枚举类型的特殊实现,它使用位向量来存储枚举值。
  5. 其他实现:还有其他一些实现,如 LinkedHashSet、SortedSet 等,它们也基于哈希表或红黑树。

📝 注意:Set 集合的实现原理因具体实现而异,但通常情况下,Set 集合的实现原理都基于哈希表或哈希映射。

Set 集合常见面试问题 🤔


🔍 基础概念类问题

1. Set 和 List 的主要区别是什么?

答:

特性SetList
元素唯一性✅ 不允许重复❌ 允许重复
顺序保证大部分无序(除LinkedHashSet, TreeSet)✅ 保持插入顺序
索引访问❌ 不支持✅ 支持按索引访问
实现类HashSet, TreeSet, LinkedHashSetArrayList, LinkedList, Vector

2. 为什么 Set 不允许重复元素?

答: Set 的"唯一性"是通过元素的 hashCode()equals() 方法实现的。当添加新元素时:

  1. 先计算哈希值定位存储位置
  2. 如果该位置有元素,调用 equals() 比较
  3. 如果返回 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 的区别?

答:

特性HashSetLinkedHashSetTreeSet
底层实现HashMapLinkedHashMap红黑树
性能查询最快 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 通过两种方式排序:

  1. 自然排序:元素实现 Comparable 接口
  2. 定制排序:创建 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]