本文目录导读:

HashSet 在 Java 中保证元素不重复的核心机制是基于 HashMap 实现的,它利用哈希表(Hash Table)的特性,通过以下步骤来确保元素的唯一性:
底层数据结构
HashSet 内部维护了一个 HashMap 实例。
// HashSet 源码关键部分
private transient HashMap<E,Object> map;
// 一个虚拟的占位对象
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
当向 HashSet 添加元素时,实际上是将该元素作为 HashMap 的键(Key) 存入,而值(Value)则统一使用一个固定的虚拟对象 PRESENT。
添加元素时的唯一性校验流程
当调用 add(E e) 方法时,代码如下:
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
HashMap.put(key, value) 方法的核心逻辑:
- 计算哈希值:调用
key.hashCode()方法获取元素的哈希码。 - 定位桶位置:根据哈希码计算元素在数组(table)中的索引位置。
- 查找重复元素:
- 如果在对应位置的链表或红黑树中,没有找到与该 key 哈希值相等且
equals()返回true的节点,则将该 key-value 对插入,并返回null。 - 找到了 相同的 key(即
hashCode相等且equals()返回true),则用新 value 替换旧 value,并返回旧 value(即非 null)。
- 如果在对应位置的链表或红黑树中,没有找到与该 key 哈希值相等且
HashSet.add() 通过判断 map.put(e, PRESENT) == null 来决定是否添加成功:
- 返回
null:说明 HashMap 中原本没有这个 key,插入成功,HashSet 添加成功。 - 返回非
null(即PRESENT):说明 HashMap 中已有相同的 key,只是替换了 value,但 HashSet 认为元素已存在,添加失败。
必须遵守的契约:hashCode() 与 equals()
HashSet 判断两个元素是否相等的依据是两个方法的组合:
- 先比较
hashCode():- 如果两个对象的
hashCode()不同,则直接判定为不同元素,不会调用equals()。
- 如果两个对象的
- 再比较
equals():- 如果两个对象的
hashCode()相同(发生了哈希冲突),则进一步调用equals()方法判断是否真正相等。 - 只有当
equals()返回true时,才认为是重复元素。
- 如果两个对象的
放入 HashSet 的自定义对象必须正确重写 hashCode() 和 equals() 方法,否则可能导致重复元素无法被识别的问题。
举例说明
Set<String> set = new HashSet<>();
set.add("apple"); // "apple".hashCode() 计算出索引,放入
set.add("banana"); // 不同 hash,直接放入
set.add("apple"); // 相同 hash,equals 比较返回 true,map.put 返回旧值,add 返回 false
set 中只有两个元素:["apple", "banana"]。
核心保证机制
| 步骤 | 操作 | 目的 |
|---|---|---|
| 1 | 计算 hashCode() |
快速定位桶位置,缩小比较范围 |
| 2 | 比较 hashCode() |
如果不同,直接判定不同元素 |
| 3 | 比较 equals() |
hashCode() 相同,做精准确认 |
| 4 | 利用 HashMap 的 put 返回值 | 返回 null 表示新插入,返回非 null 表示重复 |
HashSet 保证元素不重复的本质是 “HashMap的键不重复”,而 HashMap 依赖 hashCode() 和 equals() 的配合来完成去重。