HashSet底层完全依赖HashMap实现,其元素作为HashMap的key存储,value统一为静态占位对象PRESENT;add、contains等操作均直接委托HashMap对应方法,去重依赖key唯一性及hashCode/equals机制。

HashSet 的底层实现确实完全依赖于 HashMap,它本身不维护独立的哈希表结构,而是“借壳”HashMap 来实现集合语义。
核心设计:用 HashMap 存键,忽略值
HashSet 内部持有一个 transient HashMap<E, Object> 实例(JDK 8+),其中:
- 泛型类型 E 对应 HashSet 存储的元素类型;
- HashMap 的 key 存的就是 HashSet 的元素;
- 所有 value 都是同一个静态的哑对象 —— PRESENT(类型为 Object,仅作占位,不携带意义)。
也就是说,set.add("a") 实际执行的是 map.put("a", PRESENT);set.contains("a") 等价于 map.containsKey("a")。
为什么能复用 HashMap 实现无序、去重、O(1) 操作?
因为 HashSet 所需的核心能力,HashMap 全都天然支持:
立即学习“Java免费学习笔记(深入)”;
- 去重:HashMap 的 key 不允许重复,put 相同 key 会覆盖旧值,返回旧 value —— HashSet 利用这一点判断是否新增成功;
- 无序性:HashMap(非 LinkedHashMap)不保证遍历顺序,与 HashSet 的 contract 一致;
- O(1) 平均时间复杂度:基于哈希桶 + 链表/红黑树的查找机制,add/contains/remove 均摊成本为常数级;
- null 支持:HashMap 允许一个 null key,所以 HashSet 也允许存一个 null 元素。
关键方法如何映射到 HashMap 操作?
翻看 JDK 源码(如 HashSet.add())可看到直白的委托逻辑:
-
add(E e)→ 调用map.put(e, PRESENT) == null(返回 true 表示新增); -
remove(Object o)→ 调用map.remove(o) != null; -
contains(Object o)→ 调用map.containsKey(o); -
size()→ 直接返回map.size(); -
iterator()→ 返回map.keySet().iterator()(注意不是 values 或 entrySet)。
小结:轻量封装,零冗余存储
HashSet 没有自己实现哈希计算、扩容、冲突处理或树化逻辑,所有底层工作都由 HashMap 完成。它只是一个语义适配层:把 “键值对容器” 的 key 集合,包装成 “不重复元素集合”。这种设计避免了代码重复,也确保了行为一致性 —— 比如当 HashMap 修复 hash 碰撞 bug 时,HashSet 自动受益。


















