JDK 8 HashSet 详解及详细源码展示

JDK 8 中的 HashSet 是基于 HashMap 实现的 无序集合,它继承自 AbstractSet 并实现了 Set 接口。HashSet 的核心特性是元素唯一(通过 HashMap 键的唯一性保证)、允许 null 元素,但不保证元素的顺序(迭代顺序与插入顺序无关)。

一、HashSet 的核心特性

特性说明
无序性迭代顺序不保证(与插入顺序或哈希值无关)。
元素唯一元素不可重复(依赖 HashMap 键的唯一性)。
允许 null 元素允许存储一个 null 元素(HashMap 允许 null 键)。
时间复杂度插入、删除、查找等操作的平均时间复杂度为 O(1)(基于哈希表的理想情况)。
非线程安全多线程环境下需手动同步(如使用 Collections.synchronizedSet 包装)。

二、HashSet 的核心结构

1. 继承与接口
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable
  • AbstractSet:提供 Set 接口的骨架实现(如 size()isEmpty() 等方法的默认实现)。
  • Set:定义集合的核心操作(如 addremovecontains 等)。
2. 核心属性

HashSet 内部通过 HashMap 存储元素,元素作为 HashMap 的键,值为静态的 Object 占位符(仅用于标记存在):

// 内部 HashMap 实例(存储元素)
private transient HashMap<E, Object> map;

// 值的占位符(无实际意义,仅用于 HashMap 存储)
private static final Object PRESENT = new Object();
3. 构造方法

HashSet 的构造方法本质是初始化内部的 HashMap

// 默认构造方法:初始容量 16,负载因子 0.75
public HashSet() {
    map = new HashMap<>();
}

// 指定初始容量的构造方法
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}

// 指定初始容量和负载因子的构造方法
public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}

// 从 Iterable 初始化(添加所有元素)
public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size() / .75f) + 1, 16)); // 计算初始容量
    addAll(c); // 添加所有元素
}

三、核心方法解析

HashSet 的方法本质上是对内部 HashMap 的封装,核心逻辑依赖 HashMap 的实现。

1. add(E e):添加元素

add 方法通过 HashMap.put(e, PRESENT) 实现,利用 HashMap 键的唯一性保证元素不重复:

public boolean add(E e) {
    return map.put(e, PRESENT) == null; // HashMap 的 put 返回旧值(若存在则返回旧值,否则返回 null)
}
  • 若元素已存在(map.put 返回非 null),则返回 false
  • 若元素不存在(map.put 返回 null),则返回 true
2. contains(Object o):判断元素是否存在

通过 HashMap.containsKey(o) 实现,依赖 HashMap 的哈希查找逻辑:

public boolean contains(Object o) {
    return map.containsKey(o); // HashMap 的 containsKey 基于哈希表查找
}
3. remove(Object o):删除元素

通过 HashMap.remove(o) 实现,删除成功返回 true(若元素存在):

public boolean remove(Object o) {
    return map.remove(o) == PRESENT; // HashMap 的 remove 返回旧值(存在则返回 PRESENT,否则返回 null)
}
4. iterator():返回迭代器

HashSet 的迭代器按 HashMap 键的遍历顺序返回元素(无序):

public Iterator<E> iterator() {
    return map.keySet().iterator(); // 返回 HashMap 键集的迭代器(无序)
}
5. clear():清空集合

通过 HashMap.clear() 清空所有元素:

public void clear() {
    map.clear();
}

四、关键源码细节

1. 序列化与反序列化

HashSet 实现了 Serializable 接口,序列化时会保存 map 字段;反序列化时通过 readObject 方法重建 map

// 序列化时写入 map
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
    s.defaultWriteObject();
    s.writeInt(map.capacity()); // 写入初始容量
    s.writeFloat(map.loadFactor()); // 写入负载因子
    s.writeInt(map.size()); // 写入元素数量
    for (E e : map.keySet()) {
        s.writeObject(e); // 写入所有元素
    }
}

// 反序列化时读取 map
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();
    int capacity = s.readInt();
    float loadFactor = s.readFloat();
    int size = s.readInt();
    map = new HashMap<>(capacity, loadFactor); // 重建 HashMap
    for (int i = 0; i < size; i++) {
        E e = (E) s.readObject();
        map.put(e, PRESENT); // 重新添加元素
    }
}
2. 克隆(Cloneable)

HashSet 实现了 Cloneable 接口,clone() 方法返回浅拷贝的 HashSet(内部 map 是原 map 的引用):

public Object clone() {
    try {
        HashSet<E> newSet = (HashSet<E>) super.clone();
        newSet.map = (HashMap<E, Object>) map.clone(); // 浅拷贝 map(键值对引用相同)
        return newSet;
    } catch (CloneNotSupportedException e) {
        throw new InternalError(e);
    }
}

五、性能与注意事项

1. 性能特点
  • 添加/查找/删除:平均时间复杂度为 O(1)(哈希冲突少时),最坏情况为 O(n)(哈希冲突严重时,退化为链表)。
  • 空间复杂度:需预分配额外空间(负载因子 0.75 是空间与时间的权衡)。
2. 注意事项
  • 线程不安全:多线程环境下同时修改 HashSet 可能导致数据不一致(如 ConcurrentModificationException)。解决方案:
    Set<E> synchronizedSet = Collections.synchronizedSet(new HashSet<>());
    
  • 迭代器的快速失败(Fail-Fast):迭代过程中若集合被修改(非通过迭代器的 remove 方法),会抛出 ConcurrentModificationException。这是通过 modCount 字段实现的:
    // AbstractSet 中的 modCount 字段(继承自 AbstractCollection)
    protected transient int modCount = 0;
    
    // 迭代器检查 modCount
    private class Itr implements Iterator<E> {
        int expectedModCount = modCount; // 预期的修改次数
    
        public boolean hasNext() {
            if (expectedModCount != modCount) {
                throw new ConcurrentModificationException(); // 快速失败
            }
            // ...
        }
    }
    
  • null 元素的支持HashSet 允许存储一个 null 元素(HashMap 允许 null 键),但多次添加 null 会被视为重复。

六、HashSet 与其他 Set 的对比

特性HashSetLinkedHashSetTreeSet
底层结构HashMap(哈希表)LinkedHashMap(哈希表+链表)TreeMap(红黑树)
顺序性无序插入顺序/访问顺序自然顺序/自定义比较器顺序
时间复杂度O(1)(平均)O(1)(平均)O(log n)
允许 null否(自然排序)/ 取决于比较器
适用场景快速去重、无顺序需求需要插入/访问顺序需要排序、导航操作

七、总结

HashSet 是基于 HashMap 实现的无序集合,核心优势是元素唯一高效的增删查操作(平均 O(1) 时间复杂度)。其内部通过 HashMap 存储元素(键为集合元素,值为占位符 PRESENT),排序规则由 HashMap 的哈希函数决定。实际开发中,若需要快速去重或无顺序要求的集合,HashSet 是首选;若需要保持插入顺序,选择 LinkedHashSet;若需要排序或导航操作,选择 TreeSet

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值