Java 集合Collection—Set
HashSet
HashSet = 哈希表实现的 Set,特点:
-
无序、不可重复、允许 null
-
增删查平均 O(1)
-
非线程安全
创建(3 种)
// 1. 默认初始容量 16,负载因子 0.75
Set<String> set = new HashSet<>();
// 2. 指定初始容量(减少 rehash)
Set<String> set = new HashSet<>(100);
// 3. 通过已有集合快速去重
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 3));
// 结果 [1, 2, 3]
基本 CRUD
set.add("A"); // 增,返回 boolean(已存在返回 false)
set.remove("A"); // 删,返回 boolean
set.contains("A"); // 查,返回 boolean
set.size(); // 元素个数
set.isEmpty(); // 是否空
set.clear(); // 清空
遍历(3 样)
// 1. for-each(最常用)
for (String s : set) System.out.println(s);
// 2. 迭代器(遍历中删除安全)
Iterator<String> it = set.iterator();
while (it.hasNext()) if (it.next().equals("A")) it.remove();
// 3. Lambda(Java 8+)
set.forEach(System.out::println);
批量/工具操作
// 去重(一行代码)
List<String> noDup = new ArrayList<>(new HashSet<>(oldList));
// 并集、交集、差集
Set<Integer> a = Set.of(1, 2, 3);
Set<Integer> b = Set.of(2, 3, 4);
Set<Integer> union = new HashSet<>(a); // 并集
union.addAll(b); // [1, 2, 3, 4]
Set<Integer> retain = new HashSet<>(a); // 交集
retain.retainAll(b); // [2, 3]
Set<Integer> diff = new HashSet<>(a); // 差集
diff.removeAll(b); // [1]
线程安全替代
// 1. 同步包装(读写全串行)
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
// 2. 高并发且读远多于写
Set<String> cowSet = ConcurrentHashMap.newKeySet(); // Java 8+
背口诀
“HashSet 哈希去重 O(1),无序不可重复 null 可以;批量交并差一行搞定,线程安全用同步或 ConcurrentHashMap.newKeySet()”。
HashSet原理
HashSet 确实只是“HashMap 的马甲”,内部用 固定哑值(PRESENT) 当 value,只关心 key 的哈希与相等性,达到“去重集合”的效果。
成员变量:背后就是一张 HashMap
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
private transient HashMap<E,Object> map; // 核心存储
// 哑值,所有 key 都指向它
private static final Object PRESENT = new Object();
}
构造器:直接 new HashMap
public HashSet() {
map = new HashMap<>();
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
add/remove/contains 就是 HashMap 的 put/remove/containsKey
public boolean add(E e) {
return map.put(e, PRESENT) == null; // put 返回 null 说明原来没有 → 添加成功
}
public boolean remove(Object o) {
return map.remove(o) == PRESENT; // 返回 PRESENT 说明原来有 → 删除成功
}
public boolean contains(Object o) {
return map.containsKey(o); // 直接调 HashMap 方法
}
迭代器:直接代理 HashMap 的 keySet
public Iterator<E> iterator() {
return map.keySet().iterator();
}
内存与性能
| 维度 | 说明 |
|---|---|
| 内存 | 每个元素多一个 Object 引用(PRESENT),几乎可以忽略 |
| 时间 | 完全等于 HashMap 操作,O(1) 均摊 |
| 线程安全 | 与 HashMap 一样 非同步 |
一句话背板
“HashSet = HashMap<PRESENT>”
LinkedHashSet
LinkedHashSet = 哈希表 + 双向链表,
既保 唯一性 又保 插入顺序(或 访问顺序),迭代性能高,其余 API 与 HashSet 完全一致。
创建(3 种)
// 1. 默认初始容量 16,负载因子 0.75,保插入顺序
Set<String> set = new LinkedHashSet<>();
// 2. 指定容量
Set<String> set = new LinkedHashSet<>(100);
// 3. 通过已有集合快速去重并保序
Set<Integer> set = new LinkedHashSet<>(Arrays.asList(3, 3, 1, 2));
// 迭代顺序:[3, 1, 2]
基本 CRUD(与 HashSet 相同)
set.add("A"); // 增,返回 boolean(已存在返回 false)
set.remove("A"); // 删,返回 boolean
set.contains("A"); // 查,返回 boolean
set.size(); // 元素个数
set.isEmpty(); // 是否空
set.clear(); // 清空
遍历(顺序保证)
// 迭代顺序 = 插入顺序
for (String s : set) System.out.println(s);
// Java 8+ Lambda
set.forEach(System.out::println);
批量/工具操作
// 去重并保插入顺序(一行代码)
List<String> noDup = new ArrayList<>(new LinkedHashSet<>(oldList));
// 交并差(与 HashSet 用法完全一致)
Set<Integer> a = new LinkedHashSet<>(List.of(1, 2, 3));
Set<Integer> b = Set.of(2, 3, 4);
Set<Integer> union = new LinkedHashSet<>(a);
union.addAll(b); // [1, 2, 3, 4](插入序)
Set<Integer> retain = new LinkedHashSet<>(a);
retain.retainAll(b); // [2, 3](插入序)
线程安全替代
// 1. 同步包装(读写全串行)
Set<String> syncSet = Collections.synchronizedSet(new LinkedHashSet<>());
// 2. 高并发且需要顺序
Set<String> cowSet = new ConcurrentSkipListSet<>(); // 并发+有序(红黑树)
背口诀
“LinkedHashSet 去重又保序,插入顺序默认齐;访问顺序构造传 true,迭代性能赛飞机”。
LinkedHashSet源码
LinkedHashSet 并不是“继承”HashSet,而是 extends HashSet 类,但真正的实现机制是“组合一个 LinkedHashMap”,因此它既复用了 HashSet 的模板逻辑,又通过 构造器注入 拿到了 LinkedHashMap 实例,从而同时拥有了 哈希去重 + 双向链表保序 的能力。
类声明:确实 extends HashSet
public class LinkedHashSet<E>
extends HashSet<E> // ← 语法层面继承
implements Set<E>, Cloneable, java.io.Serializable
HashSet 预留了“可插拔 Map”的构造器(包私有)
// 只有包内可见,让子类可以传入自己的 Map 实现
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor); // dummy 被忽略
}
LinkedHashSet 构造器:主动调用父类“Map 注入”构造器
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true); // 第三个参数 dummy 任意值
// 此时父类 map 字段已被赋为 **LinkedHashMap**
}
结果
-
父类 HashSet 的所有方法(add、remove、contains...)
底层都落在 LinkedHashMap 上,因此
-
去重逻辑 = HashMap 的 key 唯一性
-
顺序逻辑 = LinkedHashMap 的双向链表
内存/性能差异
| 维度 | HashSet | LinkedHashSet |
|---|---|---|
| 顺序 | 无序 | 插入序/访问序 |
| 节点大小 | 1 个指针 | 3 个指针(prev、next、key) |
| 迭代速度 | 慢(哈希打散) | 快(链表顺序) |
| 插入/删除 | O(1) | O(1)(略慢一点,链表维护 |
一句话总结
“语法上 extends HashSet,实现上靠 LinkedHashMap;父类负责模板,子类负责注入,于是既去重又保序。”
HashSet和LinkedHashSet使用场景
只想去重 → HashSet;
去重+保序(插入/访问)→ LinkedHashSet。
核心差异速览
| 维度 | HashSet | LinkedHashSet |
| 顺序保证 | ❌ 无序 | ✅ 插入序(或访问序) |
| 节点结构 | 纯哈希桶 | 哈希桶 + 双向链表 |
| 内存占用 | 1× 引用 | 约 1.5×(两个额外指针) |
| 插入/查询 | O(1) | O(1)(略慢于 HashSet,链表维护成本) |
| 迭代速度 | 慢(哈希打散) | 快(链表顺序遍历,缓存友好) |
典型场景对照
| 场景 | 选型 | 原因 |
|---|---|---|
| 去重后仅判断存在,不遍历 | HashSet | 内存省,插入快 |
| 去重后需要 按插入顺序 展示/序列化 | LinkedHashSet | 迭代顺序 = 插入顺序 |
| 去重后需要 按访问顺序(LRU) | LinkedHashSet(true) | 构造参数 accessOrder=true |
| 超大集合且内存极度敏感 | HashSet | 省两个指针,负载因子可控 |
| 高频迭代、低频修改 | LinkedHashSet | 链表顺序遍历,CPU 缓存命中高 |
代码秒选
普通去重
Set<String> unique = new HashSet<>(oldList);
去重并保插入顺序(UI 展示、JSON 输出)
Set<String> unique = new LinkedHashSet<>(oldList);
return new ArrayList<>(unique); // 顺序与第一次出现一致
简易 LRU(最近访问放最后)
Set<String> lru = new LinkedHashSet<>(16, 0.75f, true);
lru.add("A"); lru.add("B");
lru.contains("A"); // 访问 A
// 迭代顺序:[B, A] (A 被移到末尾)
性能量化(10 万元素)
| 操作 | HashSet | LinkedHashSet |
|---|---|---|
| 插入 | 22 ms | 26 ms |
| 随机迭代 | 55 ms | 18 ms |
| 内存占用 | ~6.4 MB | ~9.1 MB |
一句话背板
“只想去重用 HashSet,去重要保序用 LinkedHashSet;迭代多选 LinkedHashSet,内存极致选 HashSet”。
更多推荐

所有评论(0)