Java Set实现类包括HashSet(哈希表去重)、LinkedHashSet(维护插入顺序)、TreeSet(红黑树排序)、CopyOnWriteArraySet(写时复制线程安全)和ConcurrentSkipListSet(跳表高并发排序),适用于不同场景。
一、Set 接口核心特性
- 唯一性:所有元素唯一,不允许重复(通过
equals()和hashCode()判断)。 - 无序性(默认):大多数实现类不保证顺序(
TreeSet和LinkedHashSet例外)。 - 允许 null:大多数实现类允许单个
null元素(ConcurrentSkipListSet不允许)。 - 动态扩容:自动处理容量扩展(基于底层数据结构)。
二、Set 主要实现类对比
| 实现类 | 底层数据结构 | 线程安全 | 元素顺序 | 时间复杂度(插入/查询) | 适用场景 |
|---|---|---|---|---|---|
| HashSet | 哈希表(基于 HashMap) | 非线程安全 | 无序 | O(1) | 快速去重、无需顺序 |
| LinkedHashSet | 哈希表 + 双向链表 | 非线程安全 | 插入顺序 | O(1) | 需要保留插入顺序的去重 |
| TreeSet | 红黑树(基于 TreeMap) | 非线程安全 | 自然排序/定制排序 | O(log n) | 需要排序或范围查询 |
| CopyOnWriteArraySet | 动态数组(写时复制) | 线程安全 | 无序 | O(n)(写)、O(1)(读) | 高并发读、极少写操作 |
| ConcurrentSkipListSet | 跳表(Skip List) | 线程安全 | 自然排序/定制排序 | O(log n) | 高并发且需要排序的查询/写入 |
三、各实现类详解
1. HashSet
-
底层数据结构:基于
HashMap(元素作为HashMap的 key,value 统一为PRESENT静态对象)。// JDK 17 源码核心片段 public class HashSet<E> extends AbstractSet<E> { private transient HashMap<E, Object> map; private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT) == null; // 利用 HashMap 的 key 唯一性 } } -
扩容机制:与
HashMap一致,默认初始容量 16,负载因子 0.75,扩容至 2 倍。 -
线程安全:非线程安全,需通过
Collections.synchronizedSet()包装。 -
优点:
- 插入、删除、查询效率高(哈希表直接定位)。
- 内存占用较低(相比
LinkedHashSet)。
-
缺点:
- 不保证顺序。
- 哈希冲突可能影响性能(链表过长或树化)。
2. LinkedHashSet
-
底层数据结构:继承自
HashSet,内部通过LinkedHashMap实现。public class LinkedHashSet<E> extends HashSet<E> { public LinkedHashSet() { super(16, 0.75f, true); // 调用 HashSet 的特定构造方法 } } // HashSet 中隐藏的构造方法 HashSet(int initialCapacity, float loadFactor, boolean dummy) { this.map = new LinkedHashMap<>(initialCapacity, loadFactor); } -
顺序维护:通过双向链表维护插入顺序(或访问顺序,若
accessOrder为 true)。 -
扩容机制:同
HashSet,基于LinkedHashMap的哈希表扩容。 -
优点:
- 保留插入顺序,适合需要顺序遍历的场景。
- 查询效率接近
HashSet。
-
缺点:
- 内存占用略高(需维护链表指针)。
- 插入性能略低于
HashSet(需更新链表)。
3. TreeSet
-
底层数据结构:基于
TreeMap(红黑树实现)。public class TreeSet<E> extends AbstractSet<E> { private transient NavigableMap<E, Object> map; private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT) == null; // 红黑树插入 } } -
排序规则:
- 自然排序:元素实现
Comparable接口。 - 定制排序:通过
Comparator传入构造函数。
- 自然排序:元素实现
-
操作复杂度:插入、删除、查询均为 O(log n) (红黑树高度平衡)。
-
线程安全:非线程安全,需外部同步。
-
优点:
- 支持范围查询(如
subSet(),headSet(),tailSet())。 - 自动排序,适合需要有序集合的场景。
- 支持范围查询(如
-
缺点:
- 插入和删除效率低于
HashSet。 - 内存占用较高(红黑树节点结构复杂)。
- 插入和删除效率低于
4. CopyOnWriteArraySet
-
底层数据结构:基于
CopyOnWriteArrayList,通过动态数组实现写时复制。public class CopyOnWriteArraySet<E> extends AbstractSet<E> { private final CopyOnWriteArrayList<E> al; public boolean add(E e) { return al.addIfAbsent(e); // 写时复制保证唯一性 } } -
唯一性保证:在写入时遍历数组检查元素是否存在(
addIfAbsent())。 -
线程安全:通过
ReentrantLock保证写操作线程安全,读操作无锁。 -
优点:
- 读性能极高(无锁访问数组快照)。
- 适合读多写极少的高并发场景。
-
缺点:
- 写操作性能差(需复制整个数组)。
- 数据一致性弱(迭代器访问旧快照)。
5. ConcurrentSkipListSet
-
底层数据结构:基于跳表(Skip List),实现高并发排序。
public class ConcurrentSkipListSet<E> extends AbstractSet<E> { private final ConcurrentNavigableMap<E, Object> map; private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT) == null; // 跳表插入 } } -
跳表结构:
- 多层索引链表,通过概率平衡(类似平衡树)。
- 支持快速查找、插入、删除(时间复杂度 O(log n))。
-
线程安全:通过 CAS(Compare-And-Swap)和无锁算法实现高并发。
-
优点:
- 高并发下性能优于
TreeSet。 - 支持自然排序和范围查询。
- 高并发下性能优于
-
缺点:
- 内存占用高(多层索引结构)。
- 实现复杂,维护成本高。
四、扩容机制对比
| 实现类 | 初始容量 | 扩容规则 | 扩容触发条件 |
|---|---|---|---|
| HashSet | 16 | 同 HashMap(2 倍扩容) | 元素数量 > 容量 * 负载因子 |
| LinkedHashSet | 16 | 同 HashSet | 同 HashSet |
| TreeSet | 无固定容量 | 动态扩展红黑树节点 | 无需显式扩容 |
| CopyOnWriteArraySet | 0 | 每次写操作复制数组并扩展1 | 每次添加元素时触发 |
| ConcurrentSkipListSet | 无固定容量 | 跳表动态扩展层数 | 自动调整 |
五、线程安全方案对比
| 实现类 | 线程安全实现方式 | 锁粒度 | 适用场景 |
|---|---|---|---|
| CopyOnWriteArraySet | 写时复制 + ReentrantLock | 全局锁(写操作) | 读多写极少的高并发场景 |
| ConcurrentSkipListSet | CAS + 无锁跳表 | 无锁(细粒度) | 高并发读写且需要排序的场景 |
| Collections.synchronizedSet | 对象锁(synchronized) | 粗粒度 | 简单同步需求 |
六、数据结构图示(Mermaid)
ConcurrentSkipListSet
多层索引
高并发排序
跳表
CopyOnWriteArraySet
读无锁
写复制
写时复制数组
TreeSet
自动排序
支持范围查询
红黑树
LinkedHashSet
维护插入顺序
哈希表+双向链表
HashSet
数组+链表/红黑树
哈希表
七、选择建议
-
单线程场景:
- 快速去重 →
HashSet。 - 需要插入顺序 →
LinkedHashSet。 - 需要排序或范围查询 →
TreeSet。
- 快速去重 →
-
高并发场景:
- 读多写极少 →
CopyOnWriteArraySet。 - 读写均衡且需要排序 →
ConcurrentSkipListSet。
- 读多写极少 →
-
避免使用:直接使用
Collections.synchronizedSet()包装的集合(除非简单同步需求)。
八、示例代码
// TreeSet 自然排序示例
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(3);
treeSet.add(1);
treeSet.add(2);
System.out.println(treeSet); // 输出 [1, 2, 3]
// ConcurrentSkipListSet 高并发写入
Set<String> concurrentSet = new ConcurrentSkipListSet<>();
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 1000; i++) {
executor.submit(() -> concurrentSet.add("task-" + ThreadLocalRandom.current().nextInt()));
}
executor.shutdown();
九、总结
- HashSet:性能最优的无序去重集合。
- LinkedHashSet:兼顾插入顺序和查询效率。
- TreeSet:唯一支持自动排序和范围查询的集合。
- CopyOnWriteArraySet:读多写极少场景的线程安全选择。
- ConcurrentSkipListSet:高并发排序场景的终极方案。