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),性能极高
相关推荐
ulias2122 小时前
函数栈帧的创建和销毁
开发语言·数据结构·c++·windows·算法
代码探秘者2 小时前
【算法篇】3.位运算
java·数据结构·后端·python·算法·spring
玛卡巴卡ldf2 小时前
【LeetCode 手撕算法】(普通数组)53-最大子数组和、56-合并区间、189-轮转数组、238-除了自身以外数组的乘积
数据结构·算法·leetcode
j_xxx404_2 小时前
蓝桥杯基础--模拟
数据结构·c++·算法·蓝桥杯·排序算法
m0_488633322 小时前
C语言中结构体指针如何用 -> 取子数据及链表应用示例
c语言·数据结构·结构体指针·链表应用·指针操作
平生幻2 小时前
【数据结构】-复杂度
java·开发语言·数据结构
晚枫歌F3 小时前
线程池的理解使用以及代码详解
数据结构
似水এ᭄往昔3 小时前
【初阶数据结构】--排序算法
数据结构·算法·排序算法
小王不爱笑1324 小时前
HashMap 扩容全流程
java·数据结构·算法