1. Set集合基础概念解析
Set是Java集合框架中最具特色的接口之一,它代表数学意义上的"集合"概念。与List不同,Set不允许包含重复元素,这个特性在实际开发中经常被用来做数据去重。我第一次在电商项目中用到HashSet去重用户提交的SKU列表时,才真正体会到这个特性的价值。
Set接口主要实现类包括:
- HashSet:基于哈希表实现,查询效率O(1)
- TreeSet:基于红黑树实现,元素自动排序
- LinkedHashSet:维护插入顺序的HashSet
注意:Set判断元素是否重复的标准是equals()方法返回true,同时hashCode()值相同。这是面试常考点,也是实际开发中最容易踩坑的地方。
2. HashSet底层实现原理
2.1 哈希表工作机制
HashSet的底层实际上是HashMap,每个元素作为HashMap的key存储。当向HashSet添加元素时:
- 先计算元素的hashCode值
- 通过哈希函数确定存储位置
- 若该位置已有元素,则调用equals()比较
- equals()返回true则视为重复元素
java复制// 典型HashSet使用示例
Set<String> cities = new HashSet<>();
cities.add("北京"); // 哈希值计算:3117
cities.add("上海"); // 哈希值计算:2804
cities.add("北京"); // 不会重复添加
2.2 负载因子与扩容机制
HashSet有两个关键参数:
- 初始容量(默认16)
- 负载因子(默认0.75)
当元素数量达到容量*负载因子时触发扩容,每次扩容为原来2倍。我在处理10万级数据去重时,通过new HashSet(int initialCapacity)指定初始容量,避免了多次扩容带来的性能损耗。
3. TreeSet的排序特性
3.1 自然排序与定制排序
TreeSet通过两种方式实现排序:
- 自然排序:元素实现Comparable接口
- 定制排序:创建TreeSet时传入Comparator
java复制// 定制排序示例:按字符串长度排序
Set<String> words = new TreeSet<>(
(s1, s2) -> s1.length() - s2.length()
);
words.add("Java");
words.add("Python");
words.add("C");
// 输出顺序:C → Java → Python
3.2 红黑树性能分析
TreeSet的增删查操作时间复杂度都是O(log n),适合需要保持有序的场景。但在高并发环境下需要注意:
- 不是线程安全的
- 迭代器采用fail-fast机制
- 可以使用Collections.synchronizedSortedSet包装
4. Set集合实战应用
4.1 数据去重最佳实践
java复制// 从List去重的高效写法
List<Integer> numbers = Arrays.asList(1,2,2,3,3,3);
Set<Integer> uniqueSet = new HashSet<>(numbers);
List<Integer> result = new ArrayList<>(uniqueSet);
经验:当数据量超过1万时,使用LinkedHashSet可以保留插入顺序,虽然内存占用略高但可避免后续排序开销。
4.2 集合运算方法
Set接口提供了丰富的集合运算方法:
java复制Set<String> set1 = new HashSet<>(Arrays.asList("A","B","C"));
Set<String> set2 = new HashSet<>(Arrays.asList("B","C","D"));
set1.retainAll(set2); // 交集 → [B, C]
set1.addAll(set2); // 并集 → [A, B, C, D]
set1.removeAll(set2); // 差集 → [A]
5. 性能对比与选型建议
5.1 各实现类性能指标
| 操作 | HashSet | TreeSet | LinkedHashSet |
|---|---|---|---|
| 添加 | O(1) | O(log n) | O(1) |
| 删除 | O(1) | O(log n) | O(1) |
| 查找 | O(1) | O(log n) | O(1) |
| 有序性 | 无 | 排序 | 插入顺序 |
5.2 选型决策树
- 是否需要去重? → 是 → 使用Set
- 是否需要排序? → 是 → TreeSet
- 是否需要保持插入顺序? → 是 → LinkedHashSet
- 以上都不需要 → HashSet
6. 常见问题排查
6.1 元素重复问题
问题现象:明明是不同的对象,却被Set认为是重复元素。
排查步骤:
- 检查hashCode()实现是否合规
- 相同对象必须返回相同hashCode
- 不同对象尽量返回不同hashCode
- 检查equals()方法是否实现正确
- 满足自反性、对称性、传递性
- 确保hashCode()和equals()使用相同的字段比较
6.2 内存溢出问题
场景:存储大量对象时出现OOM。
解决方案:
- 调整初始容量:
new HashSet(预期大小) - 优化存储对象:
- 使用基本类型替代包装类
- 实现对象复用池
- 考虑使用布隆过滤器做前置去重
7. 高级特性与优化
7.1 并行流处理
Java 8+可以使用并行流加速大规模Set处理:
java复制Set<String> bigSet = ...;
bigSet.parallelStream()
.filter(s -> s.length() > 5)
.collect(Collectors.toSet());
7.2 不可变集合
Java 9+支持创建不可变Set:
java复制Set<String> immutable = Set.of("A", "B", "C");
注意事项:不可变集合不支持修改操作,适合配置项等场景。尝试修改会抛出UnsupportedOperationException。