Java HashSet


HashSet 是一个基于 HashMap 实现的无序列表。
它不保证数据存储的顺序, 但是可以保证存储的数据是唯一不重复的, 同时支持存储 null。

如果再了解 HashMap 后, HashSet 是几个 Collection 实现中最容易理解的集合, 因为 HashSet 的所有操作都是借助于 HashMap 实现的。

HashSet 内部声明了一个 HashMap, 然后将数据存储在 HashMap 的 key 中, 所有的 value 直接设置为同一个 Object 对象, 借助 HashMap key 的唯一性间接的实现了 HashSet 的唯一性。

1 HashSet 中的几个重要属性

public class HashSet<E> {

    // 一个静态, 不可变的 Object 变量
    // 静态保证了唯一性, 整个应用就一个, 可以节省空间
    // 不可变, 进一步保正这个对象不会被任何线程修改
    private static final Object PRESENT = new Object();

    // HashSet 中存储数据的地方
    // 就是借助 HashMap 存储的, 存入 HashSet 的数据, 统一放到这个 HashMap 的 key 中, 这个 key 对应的 value 则统一为上面定义的静态 Object
    private transient HashMap<E,Object> map;
}

2 HashSet 的构造方法

2.1 无参的构造函数

public HashSet() {
    // 为属性 map 赋值, 声明为 HashMap
    map = new HashMap<>();
}

很简单, 创建一个 HashMap, 并赋值给自己存储数据的属性 HashMap<E,Object> map

2.2 指定容量的构造函数

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

同样, 直接创建一个指定容量的 HashMap, 然后赋值给自己存储数据的属性 HashMap<E,Object> map

2.3 指定容量和负载因子的构造函数

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

依旧是直接调用对应 HashMap 的方法, 创建出需要的 HashMap, 再赋值给自己的 HashMap<E,Object> map 属性

2.4 给定一个 Collection 的构造函数

public HashSet(Collection<? extends E> c) {
    // 创建一个 HashMap 赋值到自己的属性
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    // 添加全部
    addAll(c);
}

// 将一个集合中的数据遍历添加到自己的 HashMap 中
public boolean addAll(Collection<? extends E> c) {
    boolean modified = false;
    // 依次遍历每一个元素
    for (E e : c)
        if (add(e))
            modified = true;
    return modified;
}

// 向自己的 HashMap 添加数据
public boolean add(E e) {
    // 很简单的调用 HashMap 的 put 方法, key 值为需要存入的数据, value 为 声明好的静态变量 PRESENT
    return map.put(e, PRESENT)==null;
}

2.5 一个比较特殊的构造函数: 没有提供出去的构造函数

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    // dump 参数没有作用, 只是为了重载
    // 通过这个构造函数, 可以将 HashSet 默认的 map 实现变为 LinkedHashMap, 主要是给子类 LinkedHashSet 起使用, 达到有序的效果
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

3 HashSet 的操作方法

3.1 添加数据

public class HashSet<E> {
    // 单个添加元素
    public boolean add(E e) {
        // 直接调用 map 的 put 方法
        return map.put(e, PRESENT)==null;
    }
}

3.2 其他的添加方法

public class HashSet<E> {
    // 直接添加一个集合
    public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)
            // 遍历所有的元素, 调用自身的 add 方法
            if (add(e))
                modified = true;
        return modified;
    }
}

3.3 删除数据

public class HashSet<E> {
    
    public boolean remove(Object o) {
        // 同样是调用 map 的 remove 方法
        return map.remove(o)==PRESENT;
    }
}

同样的可以通过 removeAll 直接清空 HashSet 的数据

3.4 查询数据

Set 比较特殊, 没有提供直接获取值的方法 get 方法。
因为没必要, 如果你现在已经有一个数据 a, Set.get(a), 返回结果想要什么?
如果只是单纯的是想判断对象是否存在,可以通过 contain 方法进行判断。

4 HashSet 的补充

  1. 因为 HashSet 是通过 HashMap 实现的, HashMap 的 key 可以存 null, 但是只能存一个, 所以 HashSet 支持存 null, 同时只能存一个
  2. 因为 HashMap 是非线程安全的,所以 HashSet 也是线程非安全的。
  3. HashSet 也是自定义了序列化方法
  4. HashSet 内部没有提供自己的迭代器, 他内部是通过 HashMap 的 key 迭代器实现的
  5. HashMap 支持 fail-fast 机制, 理所当然的 HashSet 也是。
  6. HashSet 的数据是无序的, 如果需要使用有序的 Set, 可以使用 LinkedHashSet, 内部是借助 LinkedHashMap 实现

  目录