Java HashSet

一、HashSet 基础认知

1. 定义与特性

  • 实现 Set 接口,底层依赖 HashMap 存储数据
  • 元素不可重复(通过 HashMap Key 唯一性保证)
  • 无序:不保证存储和遍历顺序
  • 非线程安全
  • 允许 存储一个 null 元素
  • 无索引,不能通过下标访问
  • 底层数据结构:数组 + 链表 + 红黑树(和 HashMap 1.8 完全一致)

2. 继承关系

plaintext

复制代码
java.lang.Object
  ↳ java.util.AbstractCollection<E>
        ↳ java.util.AbstractSet<E>
              ↳ java.util.HashSet<E>

实现接口:Set<E>, Cloneable, Serializable


二、底层核心原理(面试必问)

1. 底层存储结构

java

运行

复制代码
// HashSet 源码 ------ 全局变量
private transient HashMap<E, Object> map;

// 固定的静态空对象,作为所有 Key 对应的 Value
private static final Object PRESENT = new Object();

2. 核心设计思想

  • HashSet 只存元素值,把值作为 HashMap 的 Key
  • Value 统一用一个静态的 PRESENT 空对象(节省内存)
  • 利用 HashMap Key 不可重复 特性,实现 Set 元素去重
  • 所有操作本质都是调用 HashMap 对应方法

结论:掌握 HashMap = 掌握 HashSet


三、构造方法(源码)

java

运行

复制代码
// 1. 空参构造:初始化空 HashMap(默认容量16,负载因子0.75)
public HashSet() {
    map = new HashMap<>();
}

// 2. 指定初始容量
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}

// 3. 指定容量 + 负载因子
public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}

// 4. 集合转 HashSet
public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}

四、核心方法源码解析

1. add (E e) ------ 添加元素(去重核心)

java

运行

复制代码
public boolean add(E e) {
    // 本质:调用 HashMap.put(key, 固定值PRESENT)
    // put 返回 null 表示插入成功;返回旧值表示已存在
    return map.put(e, PRESENT) == null;
}

执行逻辑

  1. 把元素 e 作为 Key 存入 HashMap
  2. Value 固定为 PRESENT
  3. 如果 Key 不存在 → put 返回 null → add 返回 true(添加成功)
  4. 如果 Key 已存在 → put 返回旧值 → add 返回 false(添加失败,自动去重)

2. remove (Object o) ------ 删除元素

java

运行

复制代码
public boolean remove(Object o) {
    // 本质:调用 HashMap.remove(key)
    return map.remove(o) == PRESENT;
}

3. contains (Object o) ------ 判断是否存在

java

运行

复制代码
public boolean contains(Object o) {
    // 本质:调用 HashMap.containsKey(key)
    return map.containsKey(o);
}

4. size() / isEmpty() / clear()

java

运行

复制代码
public int size() { return map.size(); }
public boolean isEmpty() { return map.isEmpty(); }
public void clear() { map.clear(); }

五、HashSet 元素去重原理(面试高频)

1. 去重流程

  1. 调用 add() 时,先获取元素的 hashCode()
  2. 根据 hash 值计算存储位置
  3. 如果位置为空 → 直接存储
  4. 如果位置不为空 → 调用 equals() 逐一比较
  5. 结果相同 → 判定重复,不插入
  6. 结果不同 → 链表 / 红黑树追加

2. 必须同时重写 hashCode () 和 equals ()

  • 只重写 equals:相同内容对象会被当成不同元素(hashCode 不同)
  • 只重写 hashCode:无法精准判断内容相等
  • 必须两个都重写,才能保证内容相同的对象判定为重复

示例:自定义对象去重

java

运行

复制代码
class User {
    String name;
    int age;

    // 必须重写!
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age && Objects.equals(name, user.name);
    }

    // 必须重写!
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

六、HashSet 遍历方式

1. 迭代器(推荐)

java

运行

复制代码
Iterator<String> it = set.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

2. 增强 for 循环

java

运行

复制代码
for (String s : set) {
    System.out.println(s);
}

3. forEach(JDK 8+)

java

运行

复制代码
set.forEach(System.out::println);

七、HashSet vs HashMap(核心对比)

表格

特性 HashSet HashMap
实现接口 Set Map
存储数据 仅存储元素(Key) 存储Key-Value 键值对
底层依赖 基于 HashMap 实现 数组 + 链表 + 红黑树
重复处理 自动去重 Key 唯一,Value 可重复
添加方法 add(E e) put(K,V)
内存占用 更低(Value 共享同一个对象) 更高(每个键值对独立存储)

八、高频面试考点

1. HashSet 如何保证元素不重复?

  • 底层使用 HashMap ,将元素作为 Key 存储
  • HashMap 通过 hashCode() + equals() 判断 Key 是否重复
  • 重复 Key 会覆盖,不新增元素 → 实现 Set 去重

2. HashSet 是有序的吗?

  • 无序,不保证插入顺序
  • 要有序用 LinkedHashSet
  • 要排序用 TreeSet

3. HashSet 线程安全吗?

  • 不安全

  • 解决方案: java

    运行

    复制代码
    Set<String> set = Collections.synchronizedSet(new HashSet<>());

    或用 CopyOnWriteArraySet

4. 可以存储 null 吗?

  • 可以,但只能存一个 null

5. 为什么 HashSet 底层用 HashMap,不用其他集合?

  • 哈希表查询 / 插入效率 O(1)
  • 天然支持去重
  • 实现简单,复用 HashMap 所有能力

九、使用场景

  1. 去重 :快速对列表去重(一行代码)

    java

    运行

    复制代码
    List<String> list = new ArrayList<>();
    Set<String> set = new HashSet<>(list);
  2. 判断元素是否存在:contains 效率极高

  3. 无需重复、无需索引、无需排序的集合场景


十、总结(极简背诵版)

  1. HashSet = 包装过的 HashMap,只存 Key,Value 共用静态空对象
  2. 去重核心hashCode() + equals()
  3. 特性:无序、无重复、非线程安全、允许一个 null
  4. 增删改查:本质都是调用 HashMap 对应方法
  5. 自定义对象:必须重写两个方法才能正确去重
  6. 效率:接近 O (1),性能极高
相关推荐
宵时待雨4 小时前
优选算法专题6:模拟
数据结构·c++·算法·leetcode·职场和发展
Liangwei Lin4 小时前
LeetCode 35. 搜索插入位置
数据结构·算法·leetcode
L_09074 小时前
【C++】STL— 封装红黑树以实现map 和 set
数据结构·c++
此生决int5 小时前
快速复习之数据结构篇——二叉树(三)
数据结构
Liangwei Lin5 小时前
LeetCode 78. 子集
数据结构·算法·leetcode
khalil10206 小时前
代码随想录算法训练营Day-48 单调栈02 | 42. 接雨水、84.柱状图中最大的矩形
数据结构·c++·算法·leetcode·单调栈·接雨水
大大杰哥6 小时前
Java集合框架(List/Set/Queue)核心总结与代码示例
java·数据结构
多加点辣也没关系6 小时前
数据结构与算法总章
数据结构·算法
hnjzsyjyj6 小时前
洛谷 P1305:新二叉树 ← DFS
数据结构·dfs
如君愿6 小时前
考研复习 Day 34 | 习题--计算机网络 第六章(应用层 下)、数据结构 查找算法(下)
数据结构·计算机网络·考研·课后习题