HashSet去重依赖哈希值定位和equals精确比对:先用hashCode()计算桶位置,冲突时遍历桶内元素调用equals()判定是否重复;自定义类必须同时重写hashCode()和equals()以保证逻辑一致,底层基于HashMap的key唯一性实现。

HashSet 去重不是靠“记住加过谁”,而是靠一套标准化的判断流程:先快速定位,再精确比对。核心就两点——哈希值决定位置,equals 决定是否真重复。
哈希值决定元素该放哪儿
每次 add 一个对象,HashSet 先调它的 hashCode() 方法,算出一个整数。这个数经过位运算(比如 (n-1) & hash)映射成数组下标,也就是它在底层 HashMap 的“桶”位置。
- 如果那个桶是空的,直接放进去,不啰嗦
- 如果桶里已经有东西了,说明可能冲突了——不同对象算出了相同哈希值
冲突时用 equals 做最终裁决
哈希值相同只是“嫌疑”,不代表真重复。这时 HashSet 会遍历桶里的所有元素,逐个调用它们的 equals() 方法和新元素比较:
- 只要有一个返回
true,就认定“已存在”,add 返回false,不插入 - 全都不等(都返回
false),才允许插入,哪怕哈希值一样
为什么自定义类必须重写 hashCode 和 equals
Java 默认的 hashCode() 返回的是内存地址哈希,equals() 比较的是引用是否相同。两个内容一样的 Student 对象,若没重写,哈希值不同、equals 也不等,就会被当成两个不同元素放进 HashSet。
- 重写
equals():告诉程序“什么才算相等”(比如 name 和 age 都一样) - 重写
hashCode():保证 equals 相等的对象,哈希值也一定相同(否则 HashSet 找不到它) - 不重写,或只重一个,去重逻辑就会失效
底层其实是 HashMap 的 Key
HashSet 没有自己独立的存储结构,它内部持有一个 HashMap,你 add 的每个元素,实际是作为 HashMap 的 key 存进去的,value 则统一用一个空对象(PRESENT)占位。而 HashMap 的 key 天然不允许重复——这正是 HashSet 去重能力的根本来源。











