Java 集合框架进阶——Set 实现类深度解析与实战应用

Java 集合框架进阶——Set 实现类深度解析与实战应用(2026 年 3 月视角)

Set 是“唯一性”的代名词,与 List 的“顺序 + 可重复”形成鲜明对比。
2026 年(Java 26 已稳定发布),Set 本身没有新增革命性实现类,但 SequencedSet(Java 21 引入,现已全面标配)+ Immutable Set 的优化让去重场景更高效、更安全。真正拉开差距的,是根据业务语义精准选择实现类,否则会出现“HashSet 去重失效”“TreeSet 性能崩盘”“并发下异常”等生产事故。

一、主流 Set 实现类终极对比表(2026 生产必看)

实现类底层数据结构add/contains/remove迭代顺序内存占用线程安全fail-fast2026 推荐场景(优先级)典型容量规划建议
HashSetHashMap(桶 + 链表/红黑树)O(1) 均摊无序(JDK 不保证)中等★★★★★ 普通去重、黑白名单、ID 池(90% 默认)new HashSet<>(预期大小)
LinkedHashSetHashMap + 双向链表O(1)插入顺序(或访问顺序)中上★★★★★ 需要去重 + 保持顺序(LRU、操作日志、去重队列)new LinkedHashSet<>(预期)
TreeSetTreeMap(红黑树)O(log n)自然/自定义排序中等★★★★☆ 需要排序、范围查询、TopN、有序去重new TreeSet<>(Comparator)
CopyOnWriteArraySetCopyOnWriteArrayListO(n)插入顺序高(复制)是(读写分离)弱一致性(快照)★★★★☆ 读远多于写(配置、路由表、监听器集合)写频 < 1% 时使用
ConcurrentSkipListSet跳表(Skip List)O(log n)排序中等★★★☆☆ 高并发 + 需要排序(日志、排行榜)并发场景首选
EnumSetBitVector(位向量)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)

三、实战优化场景与代码(直接可抄)

  1. 容量规划 + 批量去重(性能提升 2~5 倍)
// 错误:多次 rehash
Set&lt;String> ids = new HashSet&lt;>();
for (var item : data) ids.add(item.id());

// 正确(推荐)
Set&lt;String> ids = new HashSet&lt;>(data.size());
data.forEach(d -> ids.add(d.id()));

// 或一行(Java 16+)
Set&lt;String> unique = data.stream().map(d -> d.id()).collect(Collectors.toSet());
  1. LinkedHashSet 实现 LRU 缓存(经典面试题)
class LRUCache&lt;K,V> extends LinkedHashMap&lt;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&lt;K,V> eldest) {
        return size() > maxSize;
    }
}
  1. TreeSet 范围查询实战
TreeSet&lt;Integer> scores = new TreeSet&lt;>();
// ... 添加分数
Set&lt;Integer> top10 = scores.descendingSet().headSet(100, true); // ≥100 的前10
  1. CopyOnWriteArraySet 事件监听器
private final CopyOnWriteArraySet&lt;Listener> listeners = new CopyOnWriteArraySet&lt;>();

public void fireEvent(Event e) {
    for (Listener l : listeners) {  // 读完全无锁!
        l.onEvent(e);
    }
}
  1. 不可变 Set 返回(生产最佳实践)
public Set&lt;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 年面试 / 架构高频问题

  1. HashSet 底层为什么用 HashMap?value 存的是什么?
  2. LinkedHashSet 如何同时保证 O(1) 查询和插入顺序?
  3. TreeSet 与 PriorityQueue 底层区别?使用场景如何抉择?
  4. CopyOnWriteArraySet 的迭代器为什么是弱一致性?
  5. 红黑树在 HashSet 冲突时如何转入?阈值为什么是 8?
  6. SequencedSet 接口解决了什么历史痛点?
  7. 虚拟线程 + 高并发场景下,Set 选型有哪些新考量?
  8. 如何实现一个“自动清理过期元素”的 Set(类似 Caffeine)?

你当前项目里 Set 用得最多的是哪种实现类?
是 HashSet 普通去重、LinkedHashSet 记录顺序、TreeSet 排行榜,还是 CopyOnWrite/ConcurrentSkipListSet 并发场景?
有没有踩过 hashCode/equals 失效、TreeSet 比较器坑、或迭代器异常的雷?
想再深挖哪一块(HashMap 红黑树细节、LinkedHashSet LRU 源码、ConcurrentSkipListSet 跳表原理、Set 与虚拟线程结合优化)?继续聊~

文章已创建 5205

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部