HashSet底层基于HashMap实现,元素作为key存储,value统一为静态占位符PRESENT;PRESENT是单例Object实例,不参与哈希计算,仅满足HashMap结构要求,避免null导致的语义歧义。

Java 中的 HashSet 底层并不直接使用“Dummy 对象”来存储 value,它根本**不存储 value**——HashSet 本身没有 value 的概念。
HashSet 底层实际是 HashMap
HashSet 是基于 HashMap 实现的。它的元素(即你 add 的对象)全部作为 HashMap 的 key 存储,而所有 key 对应的 value 都是同一个静态的、无意义的 dummy 对象:PRESENT。
这个 PRESENT 在 HashMap 源码中定义为:
private static final Object PRESENT = new Object();
它只是一个占位符,不携带任何业务数据,仅用于满足 HashMap “必须有 value”的结构要求。
立即学习“Java免费学习笔记(深入)”;
PRESENT 不参与哈希计算或比较
因为 HashSet 的去重和查找逻辑完全依赖 key(即元素自身)的 hashCode() 和 equals(),PRESENT 的值、哈希码、是否相等,对集合行为完全没有影响。
- 插入时:`map.put(element, PRESENT)` —— element 决定桶位置和冲突处理
- 查找时:`map.containsKey(element)` —— 只查 key,忽略 value 是什么
- 删除时:`map.remove(element)` —— 同样只认 key
内存中怎么存?就是普通对象引用
PRESENT 是一个普通的 Object 实例,在 JVM 堆上分配一次,被所有 HashSet 条目共享引用。每个哈希桶里的 Node(或 TreeNode)结构中,value 字段都指向这个唯一的 PRESENT 对象。
例如一个典型的 Node 结构(JDK 8):
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key; // 存的是 HashSet 的元素
V value; // 全部是 PRESENT 引用
Node<K,V> next;
}
为什么不用 null 而用 PRESENT?
用 null 看似更省事,但会导致语义模糊:
-
map.get(key) == null无法区分“key 不存在”和“key 存在但 value 为 null” -
HashSet需要明确知道add()是新增还是重复,底层靠put()的返回值判断,而该返回值依赖 value 是否被替换 —— 使用统一非 null 的PRESENT就能确保逻辑清晰、安全


















