
HashSet 实现去重,靠的不是自己写比较逻辑,而是把所有活儿都交给底层的 HashMap —— 元素直接当 key 存进去,value 用一个空对象 PRESENT 占位。既然 HashMap 的 key 天然不允许重复,HashSet 就“顺理成章”做到了去重。
元素作为 key 存储,value 只是占位符
HashSet 内部持有一个 HashMap 实例,调用 add(e) 时,实际执行的是:map.put(e, PRESENT)
其中 PRESENT 是一个静态的、无状态的 Object,不参与任何判断。真正起作用的只有 e:它被当作 key,完全由 HashMap 的 key 约束机制管控。
去重发生在 HashMap 的 put 过程中
HashMap 判断 key 是否已存在,分两步:
- 先计算
e.hashCode(),定位到数组中的桶(bucket)位置 - 若桶内已有节点,再逐个调用
existingKey.equals(e)比较
只要 hashCode 相同且 equals 返回 true,就认定 key 已存在,put 不插入新节点,只替换 value(即 PRESENT)。HashSet 根据 put 的返回值是否为 null,决定 add 是否成功。
自定义类必须重写 hashCode 和 equals
如果自己写的类没重写这两个方法,放进 HashSet 就大概率无法去重:
- 默认
hashCode()返回内存地址 → 内容相同的两个对象 hash 值不同 → 被分到不同桶 →equals根本不触发 - 默认
equals()是==比较地址 → 即使进了同一个桶,也会因返回 false 被当成新元素
两者必须保持一致:equals 返回 true 的对象,hashCode 必须相同;反之不强制,但建议避免大量哈希冲突。
初始容量设置影响性能,但不改变去重逻辑
new HashSet(1000) 中的 1000,其实是传给内部 HashMap 的初始数组长度:
- 它不改变去重行为,只影响扩容次数和 rehash 开销
- 若预估存 1000 个元素,按负载因子 0.75,建议
initialCapacity ≥ 1334(1000 ÷ 0.75 向上取整) - 设太小会导致多次扩容、遍历重散列所有 key,拖慢
add性能

















