Java 集合框架进阶——Set 实现类深度解析与实战应用(2026 年 3 月视角)
Set 是“唯一性”的代名词,与 List 的“顺序 + 可重复”形成鲜明对比。
2026 年(Java 26 已稳定发布),Set 本身没有新增革命性实现类,但 SequencedSet(Java 21 引入,现已全面标配)+ Immutable Set 的优化让去重场景更高效、更安全。真正拉开差距的,是根据业务语义精准选择实现类,否则会出现“HashSet 去重失效”“TreeSet 性能崩盘”“并发下异常”等生产事故。
一、主流 Set 实现类终极对比表(2026 生产必看)
| 实现类 | 底层数据结构 | add/contains/remove | 迭代顺序 | 内存占用 | 线程安全 | fail-fast | 2026 推荐场景(优先级) | 典型容量规划建议 |
|---|---|---|---|---|---|---|---|---|
| HashSet | HashMap(桶 + 链表/红黑树) | O(1) 均摊 | 无序(JDK 不保证) | 中等 | 否 | 是 | ★★★★★ 普通去重、黑白名单、ID 池(90% 默认) | new HashSet<>(预期大小) |
| LinkedHashSet | HashMap + 双向链表 | O(1) | 插入顺序(或访问顺序) | 中上 | 否 | 是 | ★★★★★ 需要去重 + 保持顺序(LRU、操作日志、去重队列) | new LinkedHashSet<>(预期) |
| TreeSet | TreeMap(红黑树) | O(log n) | 自然/自定义排序 | 中等 | 否 | 是 | ★★★★☆ 需要排序、范围查询、TopN、有序去重 | new TreeSet<>(Comparator) |
| CopyOnWriteArraySet | CopyOnWriteArrayList | O(n) | 插入顺序 | 高(复制) | 是(读写分离) | 弱一致性(快照) | ★★★★☆ 读远多于写(配置、路由表、监听器集合) | 写频 < 1% 时使用 |
| ConcurrentSkipListSet | 跳表(Skip List) | O(log n) | 排序 | 中等 | 是 | 否 | ★★★☆☆ 高并发 + 需要排序(日志、排行榜) | 并发场景首选 |
| EnumSet | BitVector(位向量) | O(1) | 枚举声明顺序 | 极低 | 否 | 是 | ★★★★★ 枚举类型去重(状态、权限、配置开关) | EnumSet.allOf / noneOf |
| Immutable Set (Set.of / copyOf) | 固定数组或专用结构 | — | 声明顺序 | 最低 | 是 | 无 | ★★★★★ 返回值、常量、配置(推荐) | Set.of() / copyOf() |
2026 关键提醒:
HashSet 在哈希冲突 ≥8 时自动转红黑树(JDK 8+ 机制仍在);
SequencedSet 接口统一提供 getFirst()、getLast()、reversed(),LinkedHashSet / TreeSet 都已实现。
二、深度源码解析(面试 + 优化必懂)
1. HashSet —— 去重王者(本质是 HashMap)
- 内部:
HashMap<E, Object>,value 固定为PRESENT - 哈希冲突处理:链表(<8)→ 红黑树(≥8)→ 链表(<6 再转回)
hashCode()+equals()决定唯一性 → 最常见坑:自定义对象未重写两者modCount+ fail-fast 迭代器- 优化:
new HashSet<>(预期大小)可减少扩容(默认 16,负载因子 0.75)
2. LinkedHashSet —— “有序去重”神器
- 继承 HashSet,额外维护双向链表(before/after)
- 支持两种顺序:
accessOrder=false(默认插入顺序) /true(LRU 模式) removeEldestEntry()可实现 LRU 缓存(容量控制)
3. TreeSet —— 排序去重(红黑树)
- 底层 TreeMap,key 就是元素
- 必须可比较(Comparable 或传入 Comparator)
subSet()、headSet()、tailSet()返回视图(类似 List.subList)- 性能:log n,但常量大,千万级以上慎用
4. CopyOnWriteArraySet
- 写操作 → 全量拷贝新数组 + ReentrantLock
- 读/迭代 → 快照(弱一致性,不抛 ConcurrentModificationException)
- 铁律:写操作频率极低才用,否则内存 + GC 爆炸
5. ConcurrentSkipListSet(高并发有序)
- 无锁跳表,CAS 操作
- 支持并发安全 + 排序,性能优于 synchronized(TreeSet)
三、实战优化场景与代码(直接可抄)
- 容量规划 + 批量去重(性能提升 2~5 倍)
// 错误:多次 rehash
Set<String> ids = new HashSet<>();
for (var item : data) ids.add(item.id());
// 正确(推荐)
Set<String> ids = new HashSet<>(data.size());
data.forEach(d -> ids.add(d.id()));
// 或一行(Java 16+)
Set<String> unique = data.stream().map(d -> d.id()).collect(Collectors.toSet());
- LinkedHashSet 实现 LRU 缓存(经典面试题)
class LRUCache<K,V> extends LinkedHashMap<K,V> {
private final int maxSize;
public LRUCache(int maxSize) {
super(maxSize, 0.75f, true); // accessOrder = true
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return size() > maxSize;
}
}
- TreeSet 范围查询实战
TreeSet<Integer> scores = new TreeSet<>();
// ... 添加分数
Set<Integer> top10 = scores.descendingSet().headSet(100, true); // ≥100 的前10
- CopyOnWriteArraySet 事件监听器
private final CopyOnWriteArraySet<Listener> listeners = new CopyOnWriteArraySet<>();
public void fireEvent(Event e) {
for (Listener l : listeners) { // 读完全无锁!
l.onEvent(e);
}
}
- 不可变 Set 返回(生产最佳实践)
public Set<String> getBlacklist() {
return Set.of("192.168.1.1", "10.0.0.1"); // 推荐
// 或动态:Set.copyOf(mutableSet);
}
四、2026 年 Set 选型决策树(直接背下来)
普通去重(无序即可)?
↓ 是 → HashSet(带初始容量)
需要保持插入顺序 + 去重?
↓ 是 → LinkedHashSet(或 LinkedHashMap.keySet)
需要排序 / 范围查询 / TopN?
↓ 是 → TreeSet(或 Stream + toCollection(TreeSet::new))
高并发读写 + 需要排序?
↓ 是 → ConcurrentSkipListSet
读远多于写 + 线程安全?
↓ 是 → CopyOnWriteArraySet
枚举类型?
↓ 是 → EnumSet(性能 + 内存碾压)
返回给外部 / 配置 / 常量?
↓ 是 → Set.of() / Set.copyOf()
五、常见踩坑与 2026 优化技巧
- 去重失效:自定义类未重写
hashCode()+equals()(或 Lombok @Data 但字段有可变值) - TreeSet 比较异常:Comparator 返回 0 但 equals 不相等 → 去重丢失
- 迭代器陷阱:HashSet 迭代时结构性修改 → ConcurrentModificationException
- 内存优化:大 Set 用
trimToSize()(HashSet 无此方法,可转 LinkedHashSet 再操作) - 并发:普通 HashSet/TreeSet 在虚拟线程时代仍需加锁或换并发版
六、2026 年面试 / 架构高频问题
- HashSet 底层为什么用 HashMap?value 存的是什么?
- LinkedHashSet 如何同时保证 O(1) 查询和插入顺序?
- TreeSet 与 PriorityQueue 底层区别?使用场景如何抉择?
- CopyOnWriteArraySet 的迭代器为什么是弱一致性?
- 红黑树在 HashSet 冲突时如何转入?阈值为什么是 8?
- SequencedSet 接口解决了什么历史痛点?
- 虚拟线程 + 高并发场景下,Set 选型有哪些新考量?
- 如何实现一个“自动清理过期元素”的 Set(类似 Caffeine)?
你当前项目里 Set 用得最多的是哪种实现类?
是 HashSet 普通去重、LinkedHashSet 记录顺序、TreeSet 排行榜,还是 CopyOnWrite/ConcurrentSkipListSet 并发场景?
有没有踩过 hashCode/equals 失效、TreeSet 比较器坑、或迭代器异常的雷?
想再深挖哪一块(HashMap 红黑树细节、LinkedHashSet LRU 源码、ConcurrentSkipListSet 跳表原理、Set 与虚拟线程结合优化)?继续聊~