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