HashSet通过内部HashMap实现去重,元素作为key、PRESENT为value;先用hashCode()计算哈希值,经扰动函数(h^(h>>>16))优化分布,再通过hash&(table.length-1)定位桶索引;桶内冲突时用equals()逐个比较,满足“相等对象必须有相同hashCode”才能正确去重。

Java 中 HashSet 利用哈希码快速定位,核心在于它不自己管理存储,而是把元素作为 key 全部委托给内部的 HashMap 处理。整个过程围绕哈希码展开,目标是把“找元素”变成“算位置”,避免逐个遍历。
哈希码计算与扰动处理
当你调用 add()、contains() 或 remove() 时,HashSet 首先调用该元素的 hashCode() 方法,得到一个原始 int 值。但这个值可能分布不均或低有效位信息少,JDK 内部会对它做一次扰动运算(比如高16位异或低16位),让哈希值的每一位都参与索引计算,减少桶分布倾斜。
按位与运算确定数组下标
扰动后的哈希值不会直接当索引用,而是和当前哈希表数组长度减一做按位与(hash & (table.length - 1))。这个操作等价于取模,但效率更高——前提是数组长度必须是 2 的幂(如默认 16、32、64…)。结果就是该元素应落入的桶(bucket)索引,时间复杂度为 O(1)。
桶内查找靠 equals 判断是否重复
定位到桶后,事情还没完:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
立即学习“Java免费学习笔记(深入)”;
- 如果桶为空,直接插入新节点(key = 元素,value = PRESENT)
- 如果桶里已有节点,就依次调用 equals() 比较:只有 hash 值相同 且 equals 返回 true,才认为是重复元素,拒绝添加
- 若链表长度 ≥ 8 且 数组长度 ≥ 64,该桶会从链表升级为红黑树,保证最坏查找为 O(log n)
为什么重写 hashCode 和 equals 缺一不可
这是实际开发中最容易出错的地方:
- 两个对象 equals() == true,但 hashCode() 不同 → 它们大概率被分到不同桶,contains() 就永远找不到,造成逻辑错误
- hashCode() 总一样,但 equals() 总返回 false → 所有元素挤进同一个桶,退化成链表遍历,性能掉到 O(n)
- 自定义类放进 HashSet 时,只要重写其中一个,就必须重写另一个,并确保“相等的对象必须有相同的哈希码”
不复杂但容易忽略。

















