Java随笔-Set家族
2026/7/23 18:24:11 网站建设 项目流程

一、整体架构图

Set (接口) ├── HashSet │ └── LinkedHashSet ├── TreeSet ├── CopyOnWriteArraySet └── ConcurrentSkipListSet └── (Java 6+)

二、详解

1. HashSet

HashSet 就是披着 Set 外衣的 HashMap——它把所有元素当作 HashMap 的 Key,用一个固定的空对象占住 Value 位,借助哈希表的 O(1) 查找实现快速去重。元素必须正确重写 hashCode() 和 equals(),否则去重逻辑会失效。

定位:最基础、最常用的去重集合,追求极致速度。

项目内容
底层HashMap<E, Object>
有序性❌ 完全无序,迭代顺序不可预测
null 支持✅ 允许一个null
线程安全❌ 非线程安全
时间复杂度O(1) — add/remove/contains
空间开销较小,仅存储 Key + 一个固定空对象

核心源码:

publicclassHashSet<E>extendsAbstractSet<E>implementsSet<E>{privatetransientHashMap<E,Object>map;privatestaticfinalObjectPRESENT=newObject();// 所有 Value 都是它publicbooleanadd(Ee){returnmap.put(e,PRESENT)==null;// 返回 null 说明是新增}}

本质:HashSet = HashMap 的 Key 集合。

去重原理
hashCode() 定位桶 → equals() 比较桶内元素 → 相同则覆盖,不同则链入。

1. 计算 hashCode() → 定位桶位置 2. 桶内无元素 → 直接插入 3. 桶内有元素 → 用 equals() 逐个比较 - equals 返回 true → 认为是重复,不插入,返回旧值 - 全部不相等 → 链入桶内(JDK8+ 可能转红黑树)

所以:元素必须正确重写 hashCode() 和 equals(),否则去重失效。

扩容机制
完全继承 HashMap 的规则:

  • 默认初始容量:16
  • 负载因子:0.75
  • 扩容时机:元素数 > 容量 × 0.75
  • 扩容方式:容量翻倍,所有元素 rehash 重新分布

2. LinkedHashSet

定位:HashSet 的"有序升级版",保留插入顺序。

项目内容
底层LinkedHashMap<E, Object>
有序性插入有序(按添加顺序迭代)
null 支持✅ 允许一个null
线程安全❌ 非线程安全
时间复杂度O(1)
空间开销略大于 HashSet,额外维护双向链表

核心机制:
在 HashMap 的每个节点上额外挂了两个指针 before / after,形成一条按插入顺序链接的双向链表。

// LinkedHashMap.Entry 继承 HashMap.Node,多了两个指针staticclassEntry<K,V>extendsHashMap.Node<K,V>{Entry<K,V>before,after;// 双向链表指针}

迭代时:遍历这条双向链表,而非哈希桶数组,因此顺序稳定。
适用场景:需要按添加顺序去重,如配置项加载、历史记录去重。

3. TreeSet

定位:有序集合,元素自动排序,支持范围查询。

项目内容
底层TreeMap<E, Object>(红黑树)
有序性自然排序或自定义比较器排序
null 支持❌ 不允许null(无法比较)
线程安全❌ 非线程安全
时间复杂度O(log n) — add/remove/contains
空间开销较大,每个节点存左右子树指针、颜色标记

核心机制

publicclassTreeSet<E>extendsAbstractSet<E>implementsNavigableSet<E>{privatetransientNavigableMap<E,Object>m;publicbooleanadd(Ee){returnm.put(e,PRESENT)==null;}}

底层是红黑树(自平衡二叉搜索树),元素按比较规则排列:

  • 元素实现 Comparable 接口(自然排序)
  • 或构造时传入 Comparator(自定义排序)

特有 API

Elower(Ee);// 严格小于 e 的最大元素Efloor(Ee);// 小于等于 e 的最大元素Eceiling(Ee);// 大于等于 e 的最小元素Ehigher(Ee);// 严格大于 e 的最小元素SortedSet<E>subSet(Efrom,Eto);// 范围子集

适用场景:需要排序、范围查询、排行榜、区间检索。

4. CopyOnWriteArraySet

定位:线程安全的"读多写少"集合,写操作复制整个数组。

项目内容
底层CopyOnWriteArrayList<E>(数组)
有序性✅ 插入有序(按数组索引)
null 支持✅ 允许null
线程安全✅ 线程安全(写时复制)
时间复杂度读 O(1),写 O(n)
空间开销写操作时翻倍(复制新数组)

核心机制

publicclassCopyOnWriteArraySet<E>extendsAbstractSet<E>{privatefinalCopyOnWriteArrayList<E>al;publicbooleanadd(Ee){returnal.addIfAbsent(e);// 先遍历检查是否存在,不存在则复制数组添加}}

写时复制(COW)

  • 读操作:直接读当前数组,无锁,极快
  • 写操作:加锁 → 复制新数组 → 修改新数组 → 替换引用

注意:add() 时先用 indexOf 遍历检查是否已存在(O(n)),再复制数组插入。所以去重效率不高,数据量大时慎用。

适用场景:事件监听器列表、配置项集合——读极多、写极少、遍历频繁。

5. ConcurrentSkipListSet

定位:线程安全的有序集合,高并发下的 TreeSet 替代品

项目内容
底层ConcurrentSkipListMap<E, Object>(跳表)
有序性✅ 自然排序或自定义排序
null 支持❌ 不允许null
线程安全✅ 线程安全(CAS + 细粒度锁)
时间复杂度O(log n)
空间开销较大,跳表多层索引

核心机制
底层是 **跳表(**Skip List)——一种用概率平衡替代严格旋转的平衡数据结构:

Level 3: 1 -------------------------> 9 Level 2: 1 ---------> 5 ---------> 9 Level 1: 1 -> 3 -> 5 -> 7 -> 9
  • 无锁读,CAS 写
  • 并发性能优于 TreeSet + synchronized
  • 支持 NavigableSet 全部范围查询 API

适用场景:高并发下需要排序、范围查询,替代 Collections.synchronizedSortedSet(new TreeSet())。

三、对比总表

特性HashSetLinkedHashSetTreeSetCopyOnWriteArraySetConcurrentSkipListSet
底层结构HashMapLinkedHashMapTreeMap(红黑树)CopyOnWriteArrayList(数组)ConcurrentSkipListMap(跳表)
有序性❌ 无序✅ 插入有序✅ 排序有序✅ 插入有序✅ 排序有序
null 元素✅ 1个✅ 1个❌ 不允许✅ 允许❌ 不允许
线程安全✅ COW✅ CAS
add 复杂度O(1)O(1)O(log n)O(n)O(log n)
contains 复杂度O(1)O(1)O(log n)O(n)O(log n)
遍历性能与容量相关与容量相关与元素数相关极快(快照)与元素数相关
内存开销中(双向链表)大(树节点)写时翻倍大(多层索引)
迭代器fail-fastfail-fastfail-fast快照(弱一致)弱一致
比较依据hashCode+equalshashCode+equalsComparable/ComparatorequalsComparable/Comparator

四、原理深度对比

集合去重方式关键点
HashSethashCode()→ 桶定位 →equals()比较哈希冲突用链表/红黑树解决
LinkedHashSet同 HashSet额外维护插入顺序链表,不影响去重
TreeSetcompareTo()/compare()比较比较结果为 0 即视为重复
CopyOnWriteArraySetequals()遍历数组查找线性扫描,效率低
ConcurrentSkipListSet跳表索引定位 →compareTo()比较CAS 保证并发安全

重要陷阱

TreeSet 的 “相等” 陷阱

TreeSet<Person>set=newTreeSet<>((a,b)->a.age-b.age);// 如果两个人 age 相同,compare 返回 0,TreeSet 认为它们是同一个元素!// 即使 name 不同,第二个也会被去重掉

CopyOnWriteArraySet 的性能陷阱

// 每次 add 都要 O(n) 扫描 + 数组复制,数据量大时极慢CopyOnWriteArraySet<Integer>set=newCopyOnWriteArraySet<>();for(inti=0;i<10000;i++){set.add(i);// 越来越慢!}

五、选择决策树

需要线程安全? ├── 是 │ ├── 需要排序/范围查询? → ConcurrentSkipListSet │ └── 读多写少、数据量小? → CopyOnWriteArraySet └── 否 ├── 需要排序/范围查询? → TreeSet ├── 需要保留插入顺序? → LinkedHashSet └── 只追求最快去重? → HashSet

六、总结

集合一句话
HashSet最快的去重桶,无序,O(1)
LinkedHashSet有记忆的去重桶,记住你来过的顺序
TreeSet会自动排队的去重桶,支持"第几名到第几名"
CopyOnWriteArraySet写一次复制全班的去重桶,读飞快、写巨慢
ConcurrentSkipListSet多人同时排队的去重桶,并发安全还能查排名

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询