一、HashSet 是什么?
HashSet 是 Java 集合框架中 java.util 包下的实现类,它实现了 Set 接口,底层基于 HashMap 实现(可以理解为 HashSet 是 HashMap 的 "马甲",只使用了 HashMap 的 key 部分,value 是一个固定的空对象)。
核心特点:
- 无序:存储的元素不会按插入顺序、大小顺序等排列(底层是哈希表,元素位置由哈希值决定)。
- 不可重复 :不允许存储重复元素(通过
hashCode()+equals()方法保证)。 - 允许 null 值:但只能有一个 null(因为不可重复)。
- 非线程安全 :多线程环境下直接使用会有并发问题,可通过
Collections.synchronizedSet(new HashSet<>())包装。 - 查询 / 增删效率高 :理想情况下时间复杂度为
O(1)(哈希值不冲突时)。
二、核心原理(去重逻辑)
HashSet 判断两个元素是否重复的规则:
- 先调用元素的
hashCode()方法,获取哈希值; - 如果哈希值不同,直接判定为不同元素,存入集合;
- 如果哈希值相同,再调用
equals()方法:equals()返回true:判定为重复元素,不存入;equals()返回false:判定为不同元素,存入(哈希冲突,会以链表 / 红黑树形式存储)。
注意:如果自定义类作为 HashSet 元素,必须重写
hashCode()和equals()方法,否则无法正确去重(默认使用 Object 类的方法,按内存地址判断)。
三、常用操作示例
下面是 HashSet 核心操作的完整代码,可直接运行:
import java.util.HashSet;
import java.util.Iterator;
public class HashSetDemo {
public static void main(String[] args) {
// 1. 创建 HashSet 对象
HashSet<String> set = new HashSet<>();
// 2. 添加元素(add)
set.add("Apple");
set.add("Banana");
set.add("Orange");
set.add("Apple"); // 重复元素,不会被添加
set.add(null); // 允许添加一个 null
System.out.println("初始集合:" + set); // 输出无序,比如 [null, Apple, Banana, Orange]
// 3. 判断元素是否存在(contains)
boolean hasBanana = set.contains("Banana");
System.out.println("是否包含Banana:" + hasBanana); // true
// 4. 获取集合大小(size)
System.out.println("集合大小:" + set.size()); // 4
// 5. 删除元素(remove)
set.remove("Orange");
set.remove(null);
System.out.println("删除后集合:" + set); // [Apple, Banana]
// 6. 遍历 HashSet(三种方式)
// 方式1:增强 for 循环(最常用)
System.out.println("增强for循环遍历:");
for (String fruit : set) {
System.out.println(fruit);
}
// 方式2:迭代器
System.out.println("迭代器遍历:");
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 方式3:forEach 方法(Java 8+)
System.out.println("forEach遍历:");
set.forEach(System.out::println);
// 7. 清空集合(clear)
set.clear();
System.out.println("清空后是否为空:" + set.isEmpty()); // true
// 8. 自定义类作为元素(需重写 hashCode 和 equals)
HashSet<Student> studentSet = new HashSet<>();
studentSet.add(new Student(1, "张三"));
studentSet.add(new Student(1, "张三")); // 重复元素,不会添加
studentSet.add(new Student(2, "李四"));
System.out.println("学生集合大小:" + studentSet.size()); // 2
}
// 自定义学生类(重写 hashCode 和 equals 保证去重)
static class Student {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
// 重写 hashCode:根据 id 和 name 生成哈希值
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
// 重写 equals:id 和 name 都相同则判定为同一对象
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student other = (Student) obj;
return id == other.id && (name == null ? other.name == null : name.equals(other.name));
}
}
}
四、HashSet vs TreeSet vs LinkedHashSet
| 特性 | HashSet | TreeSet | LinkedHashSet |
|---|---|---|---|
| 底层结构 | 哈希表(HashMap) | 红黑树(TreeMap) | 哈希表 + 双向链表 |
| 有序性 | 无序 | 自然顺序 / 自定义排序 | 插入顺序 |
| 去重规则 | hashCode() + equals() | 实现 Comparable/Comparator | hashCode() + equals() |
| 允许 null | 是(仅一个) | 否 | 是(仅一个) |
| 效率 | 高(O (1)) | 中(O (log n)) | 高(略低于 HashSet) |
| 适用场景 | 无需有序、高效去重 | 需要排序的去重场景 | 需要保留插入顺序的去重 |
总结
- HashSet 底层依赖 HashMap,核心特性是无序、不可重复、查询增删效率高 ,通过
hashCode()+equals()保证去重。 - 自定义类作为 HashSet 元素时,必须重写
hashCode()和equals(),否则无法正确去重。 - 选择 Set 实现类的核心:无需有序用 HashSet,需排序用 TreeSet,需保留插入顺序用 LinkedHashSet。
