HashSet的基于HsahMap的基础上组装起来的类,其类图继承关系如下。
从类继续关系图知道HashSet实现了Iterabel和Set、Collectionsh、Cloneable、Serializable的接口及继承AbstractSet.。
类注解获取的信息
- 底层是基于HashMap实现的,所有迭代时不能保证插入数据的按顺序迭代,也不能保证其他方式的顺序迭代。
- HashSet的add、remove、contains、size等方法耗时性能,不会随着数量的增大而增大的,由于底层是基于HashMap实现的,在不考虑哈西冲突的情况下,不管数据量多大,其时间复杂度都O(1);
- HashSet是线程不安全的,如果需要则请自行加锁,或者使用Collections.synchronizedSet。
- 如果迭代时,数据发生了改变会,会快速的失败,抛出ConcurrentModificationException异常。
HashSet的重要的成员变量
// HashSet是基于HashMap来实现的Set集合功能
private transient HashMap<E,Object> map;
// 用于放在HashMap 的默认值,为一个虚拟的值。
private static final Object PRESENT = new Object();
HashSet初始方法
- 无参方式的初始化,这种方式使用的HashMap的默认的capacity(16)值和loadFactor(0.75)值.
public HashSet() {
map = new HashMap<>();
}
- 带原始数据的初始化
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
上述代码中的Math.max ((int) (c.size ()/.75f) + 1, 16),取括号中两个数的最大值(期望的值 / 0.75+1,默认值 16),和 16 比较大小的主要是为了,如果给定 HashMap 初始容量小于 16 ,就按照 HashMap 默认的 16 初始化好了,如果大于 16,就按照给定值初始化。而(c.size()/.75) + 1 主要是因为根据HashMap的扩容伐值的计算公式是:Map 的容量 * 0.75f,一旦达到阀值就会扩容,此处用 (int) (c.size ()/.75f) + 1 来表示初始化的值,这样使我们期望的大小值正好比扩容的阀值还大 1,就不会扩容,符合 HashMap 扩容的公式。
- 传入带参的initialCapacity和loadFactor的初始化方式初始化
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
- 传入带参的initialCapacity方式的初始化
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
- 传入带参的initialCapacity和loadFactor及dummy参数的初始化,这个构造方法是私有的,仅有LinkedHashSet使用。HashMap实例是具有指定初始名称的LinkedHashMap
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
HashSet 的新增方法
其新增方法直接调用HashMap的put方法,HashSet的新增值为HashMap的key,value值为虚拟值PRESENT
public boolean add(E e) {
// 因为HashMap 成功添加时,然后的是null,如果返回的值为null则表明成功。反之失败。
return map.put(e, PRESENT)==null;
}
HashSet 的是否包含方法
其是否包含方法也是直接调用HashMap的contains方法,以查找的值containsKeys()的参数,具体源码表现如:
public boolean contains(Object o) {
return map.containsKey(o);
}
HashSet很多方法都是直接调用,HashMap的方法的,如size()、iterator()、isEmpty()、remove(Object o)、clear()。
HashSet如何做序列化和泛序列化操作?
从HashSet源码中我们得知private transient HashMap<E,Object> map;
成员变量是被transient修饰不参与对象的序列化和反序列操作的。而HashSet是自己提供了两个私有方法来自己内部自己实现序列化和反序列化操作分别为:writeObject()和readObject().如果采用外部序列化法实现数组的序列化,会序列化整个map。HashSet 为了避免这些没有存储数据的内存空间被序列化,内部提供了两个私有方法 writeObject 以及 readObject 来自我完成序列化与反序列化,从而在序列化与反序列化数组时节省了空间和时间。因此使用 transient 修饰数组,是防止对象数组被其他外部方法序列化。
总结
阅读HashSet源码受到的启发就是可以通过组合的方式,使用已有的资源来实现所需的功能避免重复造轮子。可以看到组合的好处,方法的入参、名称、返回值都可以自定义,如果是继承的话就不行了。