HashSet去重的本质是将元素作为HashMap的key存储,利用HashMap的key不可重复特性实现;其add操作实际调用map.put(e, PRESENT),通过hashCode定位桶、equals判断逻辑相等,故自定义类必须正确重写hashCode和equals方法。

HashSet 去重的本质,是把元素作为 key 存进底层的 HashMap,而 HashMap 的 key 不允许重复——这是整个机制最核心的一点。
元素被当作 HashMap 的 key 来存储
HashSet 内部持有一个 HashMap 实例(JDK 8 中叫 map),当你调用 add(e) 时,实际执行的是:
map.put(e, PRESENT);
其中 PRESENT 是一个静态的、无意义的 Object 占位符(value 不参与去重逻辑)。
去重靠的是 HashMap 的 put 方法行为
HashMap 的 put(key, value) 在遇到相同 key 时:
- 会先计算 key 的 hashCode(),定位到对应桶(bucket)
- 在该桶中遍历节点,用 equals() 判断是否已有相同 key
- 如果找到 equals 返回 true 的节点,则用新 value 覆盖旧 value,并返回旧 value
- HashSet 利用这个返回值是否为 null 来判断是否“新增成功”:返回 null 表示没覆盖 → 元素首次加入;否则表示已存在 → add 返回 false
所以你必须正确实现 hashCode 和 equals
如果自定义类要放进 HashSet,不重写这两个方法,会导致:
- hashCode() 默认基于内存地址:不同对象即使内容相同,hash 值也不同 → 被分配到不同桶 → equals 根本不会被调用 → 重复元素被错误接受
- equals() 默认也是 == 比较地址:即使 hash 相同、进了同一个桶,也会因 equals 返回 false 而被当成新元素
只有两者都按业务语义重写(且保持一致性),才能让 HashMap 正确识别“逻辑相等”,从而让 HashSet 真正去重。
小结:不是 HashSet 自己比,而是交给 HashMap 托管
HashSet 本身没有维护任何元素集合或比较逻辑。它只是个薄包装:
– add → map.put(key, dummy)
– contains → map.containsKey(key)
– remove → map.remove(key)
所有去重、查找、删除行为,100% 由 HashMap 的哈希表+链表/红黑树结构和 key 的 equals/hashCode 协同完成。










