hashset去重本质是将元素作为hashmap的key存储,依赖hashmap键唯一性;add()即map.put(e, present)==null,重复判断由hashcode()和equals()共同决定。

HashSet 去重机制的本质,就是把元素作为 HashMap 的 key 存进去,而 HashMap 天然不允许重复的 key —— 所以你根本不需要额外写去重逻辑,add() 方法返回 false 就代表被挡下了。
底层 add 方法只有一行关键代码
打开 HashSet.java(JDK 8+),你会看到它的 add() 方法长这样:
public boolean add(E e) {<br> return map.put(e, PRESENT) == null;<br>}
这里的 map 是一个 HashMap<e object></e>,PRESENT 是一个共享的空对象(仅作占位用)。也就是说:
→ 往 HashSet 加一个元素,等价于往 HashMap 里 put(key, value);
→ 而 HashMap 的 put 只有在 key 第一次插入或替换旧值时才返回 null 或旧 value;
→ 所以 map.put(e, PRESENT) == null 为 true,才表示“真新增”,否则就是“被去重”。
真正判断重复的是 HashMap 的 putVal 流程
顺着 map.put() 往下看,最终落到 putVal(int hash, K key, V value, ...)。它判断是否重复分三步:
- 先算哈希:调用
hash(key),本质是key.hashCode()再做扰动(h ^ (h >>> 16)),避免低比特位分布不均 - 再定桶位:用
(n - 1) & hash快速定位数组下标(n 是 table 长度,必须是 2 的幂) - 最后比内容:
- 如果桶里第一个节点
p的 key 和新 key 是同一个对象(==),直接视为重复 - 否则,若哈希值相等且
key.equals(p.key)返回true,才算重复 - 若不重复,就链到链表尾部,或插入红黑树中
- 如果桶里第一个节点
为什么必须同时重写 hashCode 和 equals
这个流程决定了两个关键约束:
- 如果两个对象
equals(true),它们的hashCode()必须返回相同值 —— 否则根本进不了同一个桶,equals根本不会被调用 - 如果两个对象
hashCode()相同,equals却返回false,它们会被放在同一桶里但视为不同元素(这是允许的,叫哈希冲突) - 反过来,只重
equals不重hashCode:两个逻辑相同的对象哈希值不同 → 分散在不同桶 → 永远不触发equals→ 去重失效
一个能跑通的自定义类示例
比如你要用 Person 对象去重,必须这样写:
@Override<br>public int hashCode() {<br> return Objects.hash(name, age); // 等价于 name.hashCode() * 31 + age<br>}<br><br>@Override<br>public boolean equals(Object o) {<br> if (this == o) return true;<br> if (o == null || getClass() != o.getClass()) return false;<br> Person person = (Person) o;<br> return age == person.age && Objects.equals(name, person.name);<br>}
只要这两方法保持逻辑一致,放进 HashSet 就能稳定去重 —— 因为 HashMap 的 key 判重逻辑完全依赖它们。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











