Set 实现
问题
HashSet、LinkedHashSet、TreeSet 分别是怎么实现的?如何保证元素不重复?
答案
HashSet
基于 HashMap 实现,元素作为 HashMap 的 key,value 统一使用一个 dummy 对象:
HashSet 核心源码(简化)
public class HashSet<E> implements Set<E> {
private transient HashMap<E, Object> map;
private static final Object PRESENT = new Object(); // 所有 value 共用
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT) == null; // key 不存在则返回 null,表示添加成功
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean remove(Object o) {
return map.remove(o) == PRESENT;
}
}
去重原理:依赖元素的 hashCode() + equals() 方法。先比较 hashCode,相同再比较 equals,两者都相同则判定为重复元素。
LinkedHashSet
继承 HashSet,底层使用 LinkedHashMap,维护插入顺序:
LinkedHashSet 核心原理
public class LinkedHashSet<E> extends HashSet<E> {
// 构造函数调用 HashSet 的特殊构造函数,内部创建 LinkedHashMap
public LinkedHashSet() {
super(16, .75f, true); // 第三个参数触发使用 LinkedHashMap
}
}
TreeSet
基于 TreeMap(红黑树)实现,元素有序(自然排序或自定义 Comparator):
TreeSet 使用示例
// 自然排序(元素实现 Comparable)
TreeSet<Integer> set1 = new TreeSet<>();
set1.add(3);
set1.add(1);
set1.add(2);
System.out.println(set1); // [1, 2, 3]
// 自定义排序
TreeSet<String> set2 = new TreeSet<>(Comparator.comparingInt(String::length));
set2.add("banana");
set2.add("apple");
set2.add("cherry");
System.out.println(set2); // [apple, banana, cherry]
三者对比
| 维度 | HashSet | LinkedHashSet | TreeSet |
|---|---|---|---|
| 底层 | HashMap | LinkedHashMap | TreeMap(红黑树) |
| 有序性 | 无序 | 插入顺序 | 排序顺序 |
| null 元素 | 允许 1 个 | 允许 1 个 | 不允许(比较时 NPE) |
| 时间复杂度 | |||
| 线程安全 | 不安全 | 不安全 | 不安全 |
常见面试问题
Q1: HashSet 如何保证元素不重复?
答案:
HashSet 底层是 HashMap,添加元素时:
- 计算元素的
hashCode()确定桶位置 - 如果桶中已有元素,通过
equals()比较内容 - 如果
hashCode()相同且equals()返回true,判定为重复,不添加
因此自定义类作为 Set 元素时,必须重写 hashCode() 和 equals() 方法。
Q2: HashSet 和 HashMap 的区别?
答案:
| 维度 | HashSet | HashMap |
|---|---|---|
| 接口 | Set | Map |
| 存储内容 | 单个对象 | 键值对 |
| 添加方法 | add(e) | put(k, v) |
| 底层实现 | 基于 HashMap | 自身就是实现 |
| hashCode 使用 | 对元素计算 | 对 key 计算 |
Q3: 如何对自定义对象去重?
答案:
必须同时重写 hashCode() 和 equals():
public class User {
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User user)) return false;
return age == user.age && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
// 现在 HashSet 可以正确去重
Set<User> users = new HashSet<>();
users.add(new User("Alice", 25));
users.add(new User("Alice", 25)); // 重复,不会添加
System.out.println(users.size()); // 1