hashset去重依赖hashmap的key唯一性机制,元素作为key存入,value为占位对象present;去重由hashmap的put过程完成,先比hashcode再比equals;自定义类须重写二者且保持一致,否则无法正确去重。

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 性能











