跳到主要内容

Java Set 集合详解

Set是Java集合框架中用于存储不重复元素的核心接口,它继承自Collection接口,提供了元素唯一性的保证。在Java开发中,Set集合被广泛应用于去重、集合运算、缓存实现等各种场景,是数据处理和算法实现的重要基础。

核心特性

Set接口 = 元素唯一性 + 无序性 + 集合运算 + 高性能查找 + 去重能力

  • 🔍 元素唯一性:集合中每个元素只能出现一次,自动拒绝重复元素
  • 🔀 无序性:大多数实现不保证元素的存储顺序(LinkedHashSet例外)
  • 🔢 集合运算:支持并集(∪)、交集(∩)、差集(-)等数学集合操作
  • 高性能查找:多数实现提供O(1)查找性能,适合频繁检索
  • 🧹 天然去重:添加重复元素时自动去重,不抛异常而是返回false

1. Set接口基础概念

1.1 什么是Set接口?

Set接口是Java集合框架中的核心接口,它继承自Collection接口,为不重复元素的集合提供了完整的抽象。

Set集合具有以下核心特征:

  • 元素唯一性:不允许存储重复的元素,每个元素在集合中只能出现一次
  • 无序性:大多数实现类不保证元素的存储顺序
  • 继承Collection:具有Collection接口的所有基本方法
  • 无索引访问:不支持通过索引直接访问元素
  • 集合运算:支持并集、交集、差集等集合运算

1.2 Set接口的重要性

重要性具体体现业务价值
数据去重自动去除重复元素保证数据的一致性和准确性
快速查找基于哈希的O(1)查找提高数据检索效率
集合运算支持复杂的集合操作简化数据处理逻辑
缓存实现天然适合缓存数据结构支持高效的缓存管理

1.3 Set接口设计原则

Set接口的设计遵循以下几个核心原则:

唯一性原则

保证集合中每个元素的唯一性,避免重复数据

无序性原则

大多数实现类不保证元素的存储顺序,提高性能

集合运算原则

支持完整的集合运算,满足数学集合论的需求

高性能原则

提供高效的查找、插入、删除操作

Set接口核心方法示例
java
1public interface Set<E> extends Collection<E> {
2
3 // ========== 基本操作 ==========
4 boolean add(E e); // 添加元素,重复时返回false
5 boolean remove(Object o); // 删除指定元素
6 boolean contains(Object o); // 检查是否包含指定元素
7
8 // ========== 集合运算 ==========
9 boolean addAll(Collection<? extends E> c); // 添加集合(并集)
10 boolean removeAll(Collection<?> c); // 删除集合(差集)
11 boolean retainAll(Collection<?> c); // 保留集合(交集)
12 void clear(); // 清空集合
13
14 // ========== 查询操作 ==========
15 int size(); // 获取元素个数
16 boolean isEmpty(); // 判断是否为空
17
18 // ========== 迭代器 ==========
19 Iterator<E> iterator(); // 获取迭代器
20}

1.4 Set接口方法分类详解

Set接口提供了丰富的方法来操作不重复元素的集合,这些方法可以分为几个主要类别:

Set基本操作方法
java
1public interface Set<E> extends Collection<E> {
2
3 // 添加和删除元素
4 boolean add(E e); // 添加元素,重复时返回false
5 boolean remove(Object o); // 删除指定元素
6 boolean contains(Object o); // 检查是否包含指定元素
7
8 // 集合信息
9 int size(); // 获取集合大小
10 boolean isEmpty(); // 判断是否为空
11 void clear(); // 清空集合
12}
方法描述返回值特殊行为
add(E e)添加元素到集合成功添加返回true,元素已存在返回false添加重复元素不会抛异常
remove(Object o)从集合移除元素存在并移除返回true,不存在返回false性能因实现类而异
contains(Object o)检查元素是否在集合中存在返回true,不存在返回falseHashSet为O(1),TreeSet为O(log n)

1.5 Set接口方法对比分析

操作类型方法名返回值说明使用场景
添加操作add(E)boolean添加元素,重复时返回false基本元素添加
删除操作remove(Object)boolean删除元素,不存在时返回false基本元素删除
查找操作contains(Object)boolean检查元素是否存在元素存在性检查
集合运算addAll(Collection)boolean并集操作合并两个集合
集合运算removeAll(Collection)boolean差集操作从集合中移除元素
集合运算retainAll(Collection)boolean交集操作保留共同元素
方法使用建议
  • 添加元素:使用add()方法,注意处理重复元素的情况
  • 删除元素:使用remove()方法,注意处理元素不存在的情况
  • 集合运算:使用addAll()、removeAll()、retainAll()进行复杂操作
  • 性能考虑:contains()操作在HashSet中为O(1),在TreeSet中为O(log n)

2. Set 实现类详解

2.1 HashSet 概述

核心特点

HashSet是Set接口最常用的实现类,基于HashMap实现,具有以下特点:

  • 🔍 基于HashMap:底层使用HashMap存储元素
  • 🔀 无序性:不保证元素的存储顺序
  • 高性能:查找、插入、删除操作平均时间复杂度O(1)
  • 允许null:支持null元素
  • ⚠️ 线程不安全:在多线程环境下需要外部同步

适用场景

  • 需要快速查找、插入、删除的场景
  • 数据去重处理
  • 缓存实现
  • 集合运算
  • 对元素顺序没有要求的场景

2.2 HashSet 内部结构

HashSet基于HashMap实现,通过HashMap的键来存储Set的元素,值使用一个固定的虚拟对象。

核心字段

HashSet核心字段
java
1public class HashSet<E> extends AbstractSet<E>
2 implements Set<E>, Cloneable, java.io.Serializable {
3
4 // 底层使用HashMap存储元素
5 private transient HashMap<E,Object> map;
6
7 // 虚拟值,用于HashMap的值部分
8 private static final Object PRESENT = new Object();
9}

构造方法

HashSet构造方法
java
1public class HashSet<E> extends AbstractSet<E> {
2
3 /**
4 * 构造一个默认初始容量的空HashSet
5 */
6 public HashSet() {
7 map = new HashMap<>();
8 }
9
10 /**
11 * 构造一个指定初始容量的空HashSet
12 */
13 public HashSet(int initialCapacity) {
14 map = new HashMap<>(initialCapacity);
15 }
16
17 /**
18 * 构造一个指定初始容量和负载因子的空HashSet
19 */
20 public HashSet(int initialCapacity, float loadFactor) {
21 map = new HashMap<>(initialCapacity, loadFactor);
22 }
23
24 /**
25 * 构造一个包含指定集合元素的HashSet
26 */
27 public HashSet(Collection<? extends E> c) {
28 map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
29 addAll(c);
30 }
31}

内存布局示意图

1HashSet 实例
2┌─────────────────────────────────────────┐
3│ map: HashMap<E,Object> │
4│ ├── "Java" → PRESENT │
5│ ├── "Python" → PRESENT │
6│ ├── "C++" → PRESENT │
7│ └── null → PRESENT │
8│ │
9│ PRESENT: Object (虚拟值) │
10└─────────────────────────────────────────┘

2.3 HashSet 核心方法实现

2.3.1 添加元素

HashSet添加元素实现
java
1public class HashSet<E> extends AbstractSet<E> {
2
3 /**
4 * 添加元素到HashSet
5 * 时间复杂度:平均O(1),最坏情况O(n)
6 */
7 public boolean add(E e) {
8 // 使用HashMap的put方法,如果键不存在则返回null
9 return map.put(e, PRESENT) == null;
10 }
11
12 /**
13 * 批量添加元素
14 * 时间复杂度:O(n),n为要添加的元素个数
15 */
16 public boolean addAll(Collection<? extends E> c) {
17 boolean modified = false;
18 for (E e : c) {
19 if (add(e)) {
20 modified = true;
21 }
22 }
23 return modified;
24 }
25}

2.3.2 删除元素

HashSet删除元素实现
java
1public class HashSet<E> extends AbstractSet<E> {
2
3 /**
4 * 从HashSet中删除指定元素
5 * 时间复杂度:平均O(1),最坏情况O(n)
6 */
7 public boolean remove(Object o) {
8 // 使用HashMap的remove方法,如果键存在则返回对应的值
9 return map.remove(o) == PRESENT;
10 }
11
12 /**
13 * 批量删除元素
14 * 时间复杂度:O(n),n为要删除的元素个数
15 */
16 public boolean removeAll(Collection<?> c) {
17 boolean modified = false;
18 for (Object o : c) {
19 if (remove(o)) {
20 modified = true;
21 }
22 }
23 return modified;
24 }
25}

2.3.3 查找元素

HashSet查找元素实现
java
1public class HashSet<E> extends AbstractSet<E> {
2
3 /**
4 * 检查HashSet是否包含指定元素
5 * 时间复杂度:平均O(1),最坏情况O(n)
6 */
7 public boolean contains(Object o) {
8 // 使用HashMap的containsKey方法
9 return map.containsKey(o);
10 }
11
12 /**
13 * 检查HashSet是否包含指定集合的所有元素
14 * 时间复杂度:O(n),n为指定集合的大小
15 */
16 public boolean containsAll(Collection<?> c) {
17 for (Object o : c) {
18 if (!contains(o)) {
19 return false;
20 }
21 }
22 return true;
23 }
24}

2.3.4 集合运算

HashSet集合运算实现
java
1public class HashSet<E> extends AbstractSet<E> {
2
3 /**
4 * 保留交集:只保留在指定集合中也存在的元素
5 * 时间复杂度:O(n),n为指定集合的大小
6 */
7 public boolean retainAll(Collection<?> c) {
8 boolean modified = false;
9 Iterator<E> it = iterator();
10 while (it.hasNext()) {
11 if (!c.contains(it.next())) {
12 it.remove();
13 modified = true;
14 }
15 }
16 return modified;
17 }
18
19 /**
20 * 清空HashSet
21 * 时间复杂度:O(n),n为集合大小
22 */
23 public void clear() {
24 map.clear();
25 }
26}

2.4 HashSet 性能分析

2.4.1 时间复杂度对比

操作时间复杂度说明
添加元素平均O(1)基于哈希值直接定位
删除元素平均O(1)基于哈希值直接定位
查找元素平均O(1)基于哈希值直接定位
遍历元素O(n)需要遍历所有元素
集合运算O(n)需要遍历集合元素

2.4.2 空间复杂度

  • 存储开销:每个元素占用一个HashMap节点
  • 哈希表开销:需要额外的哈希表数组空间
  • 负载因子:影响空间使用效率,默认0.75

2.4.3 性能优化建议

HashSet性能优化示例
java
1public class HashSetOptimization {
2
3 /**
4 * 预分配容量优化
5 */
6 public static void capacityOptimization() {
7 // 知道大概元素数量时,预分配容量
8 int expectedSize = 1000;
9 Set<String> optimizedSet = new HashSet<>(expectedSize);
10
11 // 避免频繁扩容
12 for (int i = 0; i < expectedSize; i++) {
13 optimizedSet.add("item" + i);
14 }
15 }
16
17 /**
18 * 批量操作优化
19 */
20 public static void batchOperationOptimization() {
21 Set<String> set1 = new HashSet<>();
22 Set<String> set2 = new HashSet<>();
23
24 // 批量添加,比循环add效率高
25 set1.addAll(set2);
26
27 // 批量删除
28 set1.removeAll(set2);
29
30 // 保留交集
31 set1.retainAll(set2);
32 }
33
34 /**
35 * 元素类型优化
36 */
37 public static void elementTypeOptimization() {
38 // 推荐:使用不可变对象
39 Set<String> goodSet = new HashSet<>();
40
41 // 不推荐:使用可变对象
42 Set<StringBuilder> badSet = new HashSet<>();
43 }
44}

2.5 HashSet 使用示例

2.5.1 基本操作示例

HashSet基本操作示例
java
1public class HashSetBasicExample {
2
3 public static void main(String[] args) {
4 // 创建HashSet
5 Set<String> set = new HashSet<>();
6
7 // 添加元素
8 set.add("Java");
9 set.add("Python");
10 set.add("C++");
11 set.add("JavaScript");
12
13 // 尝试添加重复元素
14 boolean added = set.add("Java");
15 System.out.println("添加重复元素Java: " + added); // false
16
17 System.out.println("Set大小: " + set.size()); // 4
18
19 // 检查元素是否存在
20 boolean contains = set.contains("Python");
21 System.out.println("是否包含Python: " + contains); // true
22
23 // 删除元素
24 boolean removed = set.remove("C++");
25 System.out.println("删除C++: " + removed); // true
26
27 // 遍历元素
28 System.out.println("Set中的元素:");
29 for (String item : set) {
30 System.out.println(item);
31 }
32
33 // 清空集合
34 set.clear();
35 System.out.println("清空后大小: " + set.size()); // 0
36 }
37}

2.5.2 集合运算示例

HashSet集合运算示例
java
1public class HashSetSetOperationsExample {
2
3 public static void main(String[] args) {
4 // 创建两个Set
5 Set<String> set1 = new HashSet<>();
6 set1.add("Java");
7 set1.add("Python");
8 set1.add("C++");
9 set1.add("JavaScript");
10
11 Set<String> set2 = new HashSet<>();
12 set2.add("Python");
13 set2.add("C++");
14 set2.add("Go");
15 set2.add("Rust");
16
17 System.out.println("Set1: " + set1);
18 System.out.println("Set2: " + set2);
19
20 // 并集操作
21 Set<String> union = new HashSet<>(set1);
22 union.addAll(set2);
23 System.out.println("并集: " + union);
24
25 // 交集操作
26 Set<String> intersection = new HashSet<>(set1);
27 intersection.retainAll(set2);
28 System.out.println("交集: " + intersection);
29
30 // 差集操作
31 Set<String> difference = new HashSet<>(set1);
32 difference.removeAll(set2);
33 System.out.println("差集(Set1 - Set2): " + difference);
34
35 // 对称差集
36 Set<String> symmetricDifference = new HashSet<>(union);
37 symmetricDifference.removeAll(intersection);
38 System.out.println("对称差集: " + symmetricDifference);
39 }
40}

2.5.3 去重示例

HashSet去重示例
java
1public class HashSetDeduplicationExample {
2
3 public static void main(String[] args) {
4 // 数组去重
5 String[] array = {"Java", "Python", "Java", "C++", "Python", "JavaScript"};
6 Set<String> uniqueSet = new HashSet<>(Arrays.asList(array));
7 String[] uniqueArray = uniqueSet.toArray(new String[0]);
8
9 System.out.println("原始数组: " + Arrays.toString(array));
10 System.out.println("去重后: " + Arrays.toString(uniqueArray));
11
12 // 列表去重
13 List<Integer> list = Arrays.asList(1, 2, 3, 2, 4, 1, 5, 3, 6);
14 Set<Integer> uniqueNumbers = new HashSet<>(list);
15 List<Integer> uniqueList = new ArrayList<>(uniqueNumbers);
16
17 System.out.println("原始列表: " + list);
18 System.out.println("去重后: " + uniqueList);
19
20 // 对象去重
21 List<Person> people = Arrays.asList(
22 new Person("Alice", 25),
23 new Person("Bob", 30),
24 new Person("Alice", 25), // 重复
25 new Person("Charlie", 35)
26 );
27
28 Set<Person> uniquePeople = new HashSet<>(people);
29 System.out.println("去重后的人数: " + uniquePeople.size());
30 uniquePeople.forEach(System.out::println);
31 }
32
33 // Person类
34 static class Person {
35 private String name;
36 private int age;
37
38 public Person(String name, int age) {
39 this.name = name;
40 this.age = age;
41 }
42
43 @Override
44 public boolean equals(Object o) {
45 if (this == o) return true;
46 if (o == null || getClass() != o.getClass()) return false;
47 Person person = (Person) o;
48 return age == person.age && Objects.equals(name, person.name);
49 }
50
51 @Override
52 public int hashCode() {
53 return Objects.hash(name, age);
54 }
55
56 @Override
57 public String toString() {
58 return "Person{name='" + name + "', age=" + age + "}";
59 }
60 }
61}

5.2.2 EnumSet 使用示例

EnumSet使用示例
java
1public class EnumSetExample {
2
3 // 颜色枚举
4 enum Color {
5 RED, GREEN, BLUE, YELLOW, BLACK, WHITE
6 }
7
8 // 权限枚举
9 enum Permission {
10 READ, WRITE, EXECUTE, DELETE
11 }
12
13 public static void main(String[] args) {
14 // 创建包含所有元素的EnumSet
15 EnumSet<Color> allColors = EnumSet.allOf(Color.class);
16 System.out.println("所有颜色: " + allColors);
17
18 // 创建空的EnumSet
19 EnumSet<Color> emptyColors = EnumSet.noneOf(Color.class);
20 emptyColors.add(Color.RED);
21 emptyColors.add(Color.GREEN);
22 System.out.println("部分颜色: " + emptyColors);
23
24 // 创建指定范围的EnumSet
25 EnumSet<Color> rangeColors = EnumSet.range(Color.RED, Color.BLUE);
26 System.out.println("范围颜色: " + rangeColors);
27
28 // 创建指定元素的EnumSet
29 EnumSet<Color> specificColors = EnumSet.of(Color.RED, Color.BLUE, Color.WHITE);
30 System.out.println("指定颜色: " + specificColors);
31
32 // 权限管理示例
33 EnumSet<Permission> userPermissions = EnumSet.of(Permission.READ, Permission.WRITE);
34 System.out.println("用户权限: " + userPermissions);
35
36 // 检查权限
37 boolean canRead = userPermissions.contains(Permission.READ);
38 boolean canExecute = userPermissions.contains(Permission.EXECUTE);
39 System.out.println("可以读取: " + canRead);
40 System.out.println("可以执行: " + canExecute);
41
42 // 添加权限
43 userPermissions.add(Permission.EXECUTE);
44 System.out.println("添加执行权限后: " + userPermissions);
45
46 // 移除权限
47 userPermissions.remove(Permission.WRITE);
48 System.out.println("移除写权限后: " + userPermissions);
49 }
50}

5.3 ConcurrentSkipListSet 实现类详解

5.3.1 ConcurrentSkipListSet 概述

核心特点

ConcurrentSkipListSet是基于ConcurrentSkipListMap实现的线程安全有序Set,具有以下特点:

  • 基于跳表:底层使用跳表数据结构
  • 线程安全:所有操作都是线程安全的
  • 有序性:元素按照自然顺序或自定义比较器排序
  • 高性能:查找、插入、删除操作时间复杂度O(log n)
  • 不允许null:不支持null元素

5.3.2 ConcurrentSkipListSet 使用示例

ConcurrentSkipListSet使用示例
java
1public class ConcurrentSkipListSetExample {
2
3 public static void main(String[] args) {
4 // 创建线程安全的有序Set
5 Set<String> set = new ConcurrentSkipListSet<>();
6
7 // 多线程并发添加元素
8 Thread t1 = new Thread(() -> {
9 for (int i = 0; i < 100; i++) {
10 set.add("Thread1-" + i);
11 }
12 });
13
14 Thread t2 = new Thread(() -> {
15 for (int i = 0; i < 100; i++) {
16 set.add("Thread2-" + i);
17 }
18 });
19
20 t1.start();
21 t2.start();
22
23 try {
24 t1.join();
25 t2.join();
26 } catch (InterruptedException e) {
27 e.printStackTrace();
28 }
29
30 System.out.println("最终大小: " + set.size());
31
32 // 有序遍历
33 System.out.println("前10个元素:");
34 int count = 0;
35 for (String item : set) {
36 if (count++ < 10) {
37 System.out.println(item);
38 } else {
39 break;
40 }
41 }
42
43 // 数字排序示例
44 Set<Integer> numberSet = new ConcurrentSkipListSet<>();
45 numberSet.add(5);
46 numberSet.add(2);
47 numberSet.add(8);
48 numberSet.add(1);
49 numberSet.add(3);
50
51 System.out.println("有序数字: " + numberSet);
52 }
53}

6. 实际应用场景

6.1 数据去重处理

数据去重处理示例
java
1public class DataDeduplicationExample {
2
3 public static void main(String[] args) {
4 // 用户ID去重
5 List<String> userIds = Arrays.asList("user1", "user2", "user1", "user3", "user2", "user4");
6 Set<String> uniqueUserIds = new HashSet<>(userIds);
7 System.out.println("去重后的用户ID: " + uniqueUserIds);
8
9 // 保持插入顺序的去重
10 List<String> orderedIds = Arrays.asList("user1", "user2", "user1", "user3", "user2", "user4");
11 Set<String> orderedUniqueIds = new LinkedHashSet<>(orderedIds);
12 System.out.println("保持顺序的去重: " + orderedUniqueIds);
13
14 // 对象去重
15 List<Employee> employees = Arrays.asList(
16 new Employee("Alice", "IT"),
17 new Employee("Bob", "HR"),
18 new Employee("Alice", "IT"), // 重复
19 new Employee("Charlie", "Finance")
20 );
21
22 Set<Employee> uniqueEmployees = new HashSet<>(employees);
23 System.out.println("去重后的员工: " + uniqueEmployees);
24 }
25
26 static class Employee {
27 private String name;
28 private String department;
29
30 public Employee(String name, String department) {
31 this.name = name;
32 this.department = department;
33 }
34
35 @Override
36 public boolean equals(Object o) {
37 if (this == o) return true;
38 if (o == null || getClass() != o.getClass()) return false;
39 Employee employee = (Employee) o;
40 return Objects.equals(name, employee.name) &&
41 Objects.equals(department, employee.department);
42 }
43
44 @Override
45 public int hashCode() {
46 return Objects.hash(name, department);
47 }
48
49 @Override
50 public String toString() {
51 return "Employee{name='" + name + "', department='" + department + "'}";
52 }
53 }
54}

6.2 集合运算应用

集合运算应用示例
java
1public class SetOperationsApplicationExample {
2
3 public static void main(String[] args) {
4 // 用户权限管理
5 Set<String> adminPermissions = new HashSet<>(Arrays.asList("read", "write", "delete", "admin"));
6 Set<String> userPermissions = new HashSet<>(Arrays.asList("read", "write"));
7 Set<String> guestPermissions = new HashSet<>(Arrays.asList("read"));
8
9 // 计算权限差异
10 Set<String> adminOnlyPermissions = new HashSet<>(adminPermissions);
11 adminOnlyPermissions.removeAll(userPermissions);
12 System.out.println("管理员独有权限: " + adminOnlyPermissions);
13
14 // 计算共同权限
15 Set<String> commonPermissions = new HashSet<>(userPermissions);
16 commonPermissions.retainAll(guestPermissions);
17 System.out.println("用户和访客共同权限: " + commonPermissions);
18
19 // 权限升级
20 Set<String> upgradedPermissions = new HashSet<>(userPermissions);
21 upgradedPermissions.addAll(Arrays.asList("delete"));
22 System.out.println("升级后的用户权限: " + upgradedPermissions);
23
24 // 标签系统
25 Set<String> user1Tags = new HashSet<>(Arrays.asList("java", "spring", "database"));
26 Set<String> user2Tags = new HashSet<>(Arrays.asList("python", "spring", "machine-learning"));
27
28 // 共同兴趣
29 Set<String> commonInterests = new HashSet<>(user1Tags);
30 commonInterests.retainAll(user2Tags);
31 System.out.println("共同兴趣: " + commonInterests);
32
33 // 推荐标签
34 Set<String> recommendedTags = new HashSet<>(user2Tags);
35 recommendedTags.removeAll(user1Tags);
36 System.out.println("推荐给用户1的标签: " + recommendedTags);
37 }
38}

6.3 缓存实现

缓存实现示例
java
1public class CacheImplementationExample {
2
3 // 简单缓存实现
4 static class SimpleCache<K, V> {
5 private final Map<K, V> cache = new HashMap<>();
6 private final Set<K> accessedKeys = new LinkedHashSet<>();
7 private final int maxSize;
8
9 public SimpleCache(int maxSize) {
10 this.maxSize = maxSize;
11 }
12
13 public V get(K key) {
14 V value = cache.get(key);
15 if (value != null) {
16 // 记录访问顺序
17 accessedKeys.remove(key);
18 accessedKeys.add(key);
19 }
20 return value;
21 }
22
23 public void put(K key, V value) {
24 if (cache.size() >= maxSize && !cache.containsKey(key)) {
25 // 移除最久未访问的元素
26 K oldestKey = accessedKeys.iterator().next();
27 accessedKeys.remove(oldestKey);
28 cache.remove(oldestKey);
29 }
30
31 cache.put(key, value);
32 accessedKeys.remove(key);
33 accessedKeys.add(key);
34 }
35
36 public void remove(K key) {
37 cache.remove(key);
38 accessedKeys.remove(key);
39 }
40
41 public int size() {
42 return cache.size();
43 }
44
45 public Set<K> keySet() {
46 return new HashSet<>(cache.keySet());
47 }
48 }
49
50 public static void main(String[] args) {
51 // 创建缓存
52 SimpleCache<String, String> cache = new SimpleCache<>(3);
53
54 // 添加元素
55 cache.put("key1", "value1");
56 cache.put("key2", "value2");
57 cache.put("key3", "value3");
58
59 System.out.println("缓存大小: " + cache.size());
60 System.out.println("缓存键: " + cache.keySet());
61
62 // 访问元素
63 cache.get("key1");
64
65 // 添加新元素,会淘汰最久未访问的
66 cache.put("key4", "value4");
67
68 System.out.println("添加key4后: " + cache.keySet());
69
70 // 使用TreeSet实现有序缓存
71 Set<String> sortedCache = new TreeSet<>();
72 sortedCache.add("item3");
73 sortedCache.add("item1");
74 sortedCache.add("item2");
75
76 System.out.println("有序缓存: " + sortedCache);
77 }
78}

6.4 黑名单/白名单系统

黑名单白名单系统示例
java
1public class BlacklistWhitelistExample {
2
3 // 黑名单系统
4 static class BlacklistSystem {
5 private final Set<String> blacklist = new HashSet<>();
6 private final Set<String> whitelist = new HashSet<>();
7
8 public void addToBlacklist(String item) {
9 blacklist.add(item);
10 whitelist.remove(item); // 从白名单中移除
11 }
12
13 public void addToWhitelist(String item) {
14 whitelist.add(item);
15 blacklist.remove(item); // 从黑名单中移除
16 }
17
18 public boolean isAllowed(String item) {
19 // 如果在黑名单中,则不允许
20 if (blacklist.contains(item)) {
21 return false;
22 }
23 // 如果白名单为空,则允许所有非黑名单项目
24 if (whitelist.isEmpty()) {
25 return true;
26 }
27 // 否则只允许白名单中的项目
28 return whitelist.contains(item);
29 }
30
31 public void removeFromBlacklist(String item) {
32 blacklist.remove(item);
33 }
34
35 public void removeFromWhitelist(String item) {
36 whitelist.remove(item);
37 }
38
39 public Set<String> getBlacklist() {
40 return new HashSet<>(blacklist);
41 }
42
43 public Set<String> getWhitelist() {
44 return new HashSet<>(whitelist);
45 }
46 }
47
48 public static void main(String[] args) {
49 BlacklistSystem system = new BlacklistSystem();
50
51 // 添加黑名单
52 system.addToBlacklist("spam@example.com");
53 system.addToBlacklist("malware.com");
54
55 // 添加白名单
56 system.addToWhitelist("trusted@company.com");
57 system.addToWhitelist("partner.com");
58
59 // 测试访问控制
60 System.out.println("spam@example.com 允许: " + system.isAllowed("spam@example.com"));
61 System.out.println("trusted@company.com 允许: " + system.isAllowed("trusted@company.com"));
62 System.out.println("unknown@example.com 允许: " + system.isAllowed("unknown@example.com"));
63
64 // 移除黑名单项目
65 system.removeFromBlacklist("spam@example.com");
66 System.out.println("移除黑名单后 spam@example.com 允许: " + system.isAllowed("spam@example.com"));
67 }
68}

7. 最佳实践总结

7.1 Set实现类选择策略

选择建议

选择合适的Set实现类需要考虑以下因素:

  • 性能要求:查找、插入、删除的性能需求
  • 顺序要求:是否需要保持元素顺序
  • 线程安全:是否在多线程环境下使用
  • 内存约束:内存使用限制
  • 功能需求:是否需要特殊功能(如导航方法)
实现类适用场景优势劣势
HashSet一般用途、去重性能最好、内存效率高无序、线程不安全
LinkedHashSet需要保持插入顺序保持顺序、性能好内存开销稍大
TreeSet需要有序、范围查询有序、支持导航方法性能较差、内存开销大
CopyOnWriteArraySet读多写少、线程安全线程安全、读性能好写性能差、内存开销大
EnumSet枚举类型性能极高、内存效率最高只能存储枚举
ConcurrentSkipListSet并发有序Set线程安全、有序性能中等

7.2 性能优化策略

Set性能优化示例
java
1public class SetPerformanceOptimization {
2
3 /**
4 * 预分配容量优化
5 */
6 public static void capacityOptimization() {
7 // 知道大概元素数量时,预分配容量
8 int expectedSize = 1000;
9 Set<String> optimizedSet = new HashSet<>(expectedSize);
10
11 // 避免频繁扩容
12 for (int i = 0; i < expectedSize; i++) {
13 optimizedSet.add("item" + i);
14 }
15 }
16
17 /**
18 * 批量操作优化
19 */
20 public static void batchOperationOptimization() {
21 Set<String> set1 = new HashSet<>();
22 Set<String> set2 = new HashSet<>();
23
24 // 批量添加,比循环add效率高
25 set1.addAll(set2);
26
27 // 批量删除
28 set1.removeAll(set2);
29
30 // 保留交集
31 set1.retainAll(set2);
32 }
33
34 /**
35 * 元素类型优化
36 */
37 public static void elementTypeOptimization() {
38 // 推荐:使用不可变对象
39 Set<String> goodSet = new HashSet<>();
40
41 // 不推荐:使用可变对象
42 Set<StringBuilder> badSet = new HashSet<>();
43 }
44
45 /**
46 * 遍历优化
47 */
48 public static void iterationOptimization() {
49 Set<String> set = new HashSet<>();
50
51 // 推荐:使用增强for循环
52for (String item : set) {
53 System.out.println(item);
54}
55
56 // 推荐:使用forEach方法
57 set.forEach(System.out::println);
58
59 // 不推荐:使用迭代器(除非需要删除元素)
60Iterator<String> iterator = set.iterator();
61while (iterator.hasNext()) {
62 String item = iterator.next();
63 System.out.println(item);
64 }
65 }
66}

7.3 常见陷阱和解决方案

注意事项
  1. equals和hashCode一致性:自定义对象必须正确实现equals和hashCode方法
  2. 可变对象问题:避免使用可变对象作为Set元素
  3. 线程安全问题:在多线程环境下选择合适的线程安全实现
  4. null值处理:某些Set实现不支持null值
  5. 性能考虑:根据使用场景选择合适的实现类
常见陷阱示例
java
1public class SetCommonTraps {
2
3 /**
4 * equals和hashCode不一致陷阱
5 */
6 public static void equalsHashCodeTrap() {
7 // 错误:equals和hashCode不一致
8 Set<BadPerson> badSet = new HashSet<>();
9 BadPerson p1 = new BadPerson("Alice", 25);
10 BadPerson p2 = new BadPerson("Alice", 25);
11
12 badSet.add(p1);
13 badSet.add(p2); // 可能添加成功,因为hashCode不同
14
15 System.out.println("BadSet大小: " + badSet.size()); // 可能是2
16
17 // 正确:equals和hashCode一致
18 Set<GoodPerson> goodSet = new HashSet<>();
19 GoodPerson p3 = new GoodPerson("Alice", 25);
20 GoodPerson p4 = new GoodPerson("Alice", 25);
21
22 goodSet.add(p3);
23 goodSet.add(p4); // 不会添加,因为equals返回true
24
25 System.out.println("GoodSet大小: " + goodSet.size()); // 1
26 }
27
28 /**
29 * 可变对象陷阱
30 */
31 public static void mutableObjectTrap() {
32 Set<StringBuilder> set = new HashSet<>();
33 StringBuilder sb = new StringBuilder("Hello");
34 set.add(sb);
35
36 System.out.println("添加前: " + set.contains(sb)); // true
37
38 // 修改对象内容
39 sb.append(" World");
40
41 System.out.println("修改后: " + set.contains(sb)); // false
42 System.out.println("Set内容: " + set);
43 }
44
45 /**
46 * 线程安全陷阱
47 */
48 public static void threadSafetyTrap() {
49 Set<String> set = new HashSet<>(); // 线程不安全
50
51 // 错误:多线程访问
52 Thread t1 = new Thread(() -> {
53 for (int i = 0; i < 1000; i++) {
54 set.add("Thread1-" + i);
55 }
56 });
57
58 Thread t2 = new Thread(() -> {
59 for (int i = 0; i < 1000; i++) {
60 set.add("Thread2-" + i);
61 }
62 });
63
64 // 正确:使用线程安全的Set
65 Set<String> safeSet = new CopyOnWriteArraySet<>();
66 // 或者使用 Collections.synchronizedSet(new HashSet<>())
67 }
68
69 static class BadPerson {
70 private String name;
71 private int age;
72
73 public BadPerson(String name, int age) {
74 this.name = name;
75 this.age = age;
76 }
77
78 @Override
79 public boolean equals(Object o) {
80 if (this == o) return true;
81 if (o == null || getClass() != o.getClass()) return false;
82 BadPerson that = (BadPerson) o;
83 return age == that.age && Objects.equals(name, that.name);
84 }
85
86 // 错误:hashCode与equals不一致
87 @Override
88 public int hashCode() {
89 return Objects.hash(name); // 只使用name
90 }
91 }
92
93 static class GoodPerson {
94 private String name;
95 private int age;
96
97 public GoodPerson(String name, int age) {
98 this.name = name;
99 this.age = age;
100 }
101
102 @Override
103 public boolean equals(Object o) {
104 if (this == o) return true;
105 if (o == null || getClass() != o.getClass()) return false;
106 GoodPerson that = (GoodPerson) o;
107 return age == that.age && Objects.equals(name, that.name);
108 }
109
110 // 正确:hashCode与equals一致
111 @Override
112 public int hashCode() {
113 return Objects.hash(name, age); // 使用所有字段
114 }
115 }
116}

7.4 测试和调试建议

Set测试调试示例
java
1public class SetTestingDebugging {
2
3 /**
4 * 单元测试示例
5 */
6 public static void unitTestExample() {
7 Set<String> set = new HashSet<>();
8
9 // 测试添加操作
10 assert set.isEmpty();
11 set.add("test");
12 assert set.size() == 1;
13 assert set.contains("test");
14
15 // 测试删除操作
16 set.remove("test");
17 assert set.isEmpty();
18
19 System.out.println("所有测试通过");
20 }
21
22 /**
23 * 性能测试示例
24 */
25 public static void performanceTestExample() {
26 int size = 100000;
27
28 // HashSet性能测试
29 long start = System.currentTimeMillis();
30 Set<Integer> hashSet = new HashSet<>();
31 for (int i = 0; i < size; i++) {
32 hashSet.add(i);
33 }
34 long hashSetTime = System.currentTimeMillis() - start;
35
36 // TreeSet性能测试
37 start = System.currentTimeMillis();
38 Set<Integer> treeSet = new TreeSet<>();
39 for (int i = 0; i < size; i++) {
40 treeSet.add(i);
41 }
42 long treeSetTime = System.currentTimeMillis() - start;
43
44 System.out.println("HashSet添加" + size + "个元素耗时: " + hashSetTime + "ms");
45 System.out.println("TreeSet添加" + size + "个元素耗时: " + treeSetTime + "ms");
46 }
47
48 /**
49 * 并发测试示例
50 */
51 public static void concurrentTestExample() {
52 Set<String> set = new CopyOnWriteArraySet<>();
53 int threadCount = 10;
54 int operationsPerThread = 1000;
55
56 // 创建多个线程并发操作
57 Thread[] threads = new Thread[threadCount];
58 for (int i = 0; i < threadCount; i++) {
59 final int threadId = i;
60 threads[i] = new Thread(() -> {
61 for (int j = 0; j < operationsPerThread; j++) {
62 set.add("Thread" + threadId + "-Item" + j);
63 }
64 });
65 }
66
67 // 启动所有线程
68 for (Thread thread : threads) {
69 thread.start();
70 }
71
72 // 等待所有线程完成
73 for (Thread thread : threads) {
74 try {
75 thread.join();
76 } catch (InterruptedException e) {
77 e.printStackTrace();
78 }
79 }
80
81 System.out.println("最终Set大小: " + set.size());
82 System.out.println("期望大小: " + (threadCount * operationsPerThread));
83 }
84}

8. 总结

Set接口作为Java集合框架的重要组成部分,提供了丰富多样的不重复元素集合实现,满足了不同场景下的需求。通过深入理解各种Set实现类的特性和使用场景,我们可以构建出高效、可靠的数据处理应用程序。

在实际开发中,需要综合考虑以下几个方面:

  • 功能需求:根据业务需求选择合适的Set类型
  • 性能要求:考虑查找、插入、删除性能
  • 内存约束:考虑内存使用效率
  • 线程安全:在多线程环境下选择合适的实现
  • 顺序要求:是否需要保持元素顺序

通过合理使用Set集合,我们可以构建出高效、可靠的Java应用程序。

9. 面试题精选

9.1 基础概念题

Q: HashSet和TreeSet的区别是什么?

A: 主要区别包括:

  • 底层实现:HashSet基于HashMap,TreeSet基于TreeMap
  • 元素顺序:HashSet无序,TreeSet有序
  • 性能特点:HashSet O(1),TreeSet O(log n)
  • null值支持:HashSet允许null,TreeSet不允许null
  • 使用场景:HashSet适合一般用途,TreeSet适合需要排序的场景

Q: HashSet如何保证元素唯一性?

A: HashSet保证元素唯一性的机制:

  • 哈希算法:计算元素的哈希值确定存储位置
  • equals比较:哈希值相同时使用equals方法比较
  • 不添加重复:如果元素已存在则不添加
  • 依赖实现:要求元素正确实现equals和hashCode方法

9.2 性能优化题

Q: 如何优化Set的性能?

A: 主要优化策略:

  • 预分配容量:使用带初始容量的构造函数
  • 批量操作:使用addAll()而非循环add()
  • 选择合适的实现类:根据使用场景选择最合适的Set
  • 避免频繁扩容:合理设置初始容量
  • 使用不可变对象:避免使用可变对象作为元素

Q: 什么场景下选择TreeSet?

A: TreeSet适用场景:

  • 需要元素有序的场景
  • 需要范围查询的场景
  • 需要获取最大最小元素的场景
  • 需要按顺序遍历的场景
  • 需要导航方法的场景

9.3 实践应用题

Q: 如何实现线程安全的Set操作?

A: 线程安全方案:

  • 使用CopyOnWriteArraySet:读多写少场景
  • 使用ConcurrentSkipListSet:需要有序的并发场景
  • Collections.synchronizedSet():包装现有Set
  • 外部同步:使用synchronized或Lock
  • 并发集合:考虑使用ConcurrentHashMap.keySet()

Q: 如何处理Set中的自定义对象?

A: 处理方案:

  • 正确实现equals和hashCode:保证一致性
  • 使用不可变对象:避免可变对象问题
  • 实现Comparable接口:用于TreeSet排序
  • 提供Comparator:自定义排序规则
  • 考虑性能影响:复杂对象的比较开销

Q: Set的遍历方式有哪些?

A: 遍历方式:

  • 增强for循环for (E item : set)
  • 迭代器Iterator<E> iterator = set.iterator()
  • forEach方法set.forEach(System.out::println)
  • Stream APIset.stream().forEach(System.out::println)
  • 并行流set.parallelStream().forEach(System.out::println)

9.4 高级应用题

Q: 如何实现一个自定义的Set?

A: 实现步骤:

  • 继承AbstractSet:实现基本的Set操作
  • 实现核心方法:add、remove、contains、size等
  • 保证元素唯一性:实现去重逻辑
  • 考虑性能优化:选择合适的底层数据结构
  • 实现迭代器:支持遍历操作

Q: EnumSet的优势和应用场景?

A: 优势和场景:

  • 性能极高:基于位向量实现
  • 内存效率:使用位运算,内存占用极小
  • 类型安全:只能存储枚举类型
  • 应用场景:权限管理、状态标记、配置选项
  • 创建方式:allOf、noneOf、range、of等方法

Q: LinkedHashSet的实现原理?

A: 实现原理:

  • 继承HashSet:复用HashSet的所有功能
  • LinkedHashMap:底层使用LinkedHashMap而非HashMap
  • 双向链表:维护元素的插入顺序
  • 性能平衡:在HashSet高性能基础上增加顺序维护
  • 构造方法:调用HashSet的特定构造方法
面试要点
  1. 理解底层实现:掌握各种Set的内部结构和工作原理
  2. 性能分析:能够分析不同操作的时间复杂度
  3. 场景选择:根据具体需求选择合适的Set实现
  4. 线程安全:理解线程安全和并发控制机制
  5. 最佳实践:了解常见陷阱和优化策略

通过本章的学习,你应该已经掌握了Java Set集合的核心概念、实现原理和最佳实践。Set是Java数据处理和算法实现的重要基础,深入理解其特性和使用场景,对于编写高效、可靠的Java程序至关重要。

评论