HashSet
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable |
HashSet是Set接口的实现,按照hash算法来存储集合中的元素,具有很好的存取和查找性能。
HashSet是无序,元素不可重复的,但是集合中的元素可以为null,只会有一个null,同时具备有Fail-Fast机制,即非线程安全的。
属性:从这里可以看到HashSet的元素是存储在HashMap的,就是利用HashMap的Key的特性,这也是为什么在此系列中,Map是先解析。
/** * 实际存储,内部元素存储到HashMap中 */ private transient HashMap<E,Object> map;
/** * value占位符:用来存储到Map的value */ private static final Object PRESENT = new Object(); |
序列化:
可以看到HashSet和ArrayList一样,实现了Serializable接口,但是源码中的内置数组是用transient来修饰的,标识不需要序列化。
所以HashSet在序列化和反序列化时,会调用writeObject/readObject()方法进行手工序列化,将HashSet的元素(即0~size-1)和容量大小写出,这样做的好处也是一样的,只保存/传输有实际意义的元素,最大限度的节约了存储、传输和处理的开销。
/** * 写出 * @param s */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { //写出非static和非transient属性 s.defaultWriteObject();
//写出map的容量和装载因子 s.writeInt(map.capacity()); s.writeFloat(map.loadFactor());
//写出元素的个数 s.writeInt(map.size());
//遍历写出所有元素 for (E e : map.keySet()) s.writeObject(e); }
/** * 流读取 * @param s */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { //读取非static和非transient属性 s.defaultReadObject();
//读取容量,并且不能小于0 int capacity = s.readInt(); if (capacity < 0) { throw new InvalidObjectException("Illegal capacity: " + capacity); }
//读取负载因子,不能为0或NaN float loadFactor = s.readFloat(); if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new InvalidObjectException("Illegal load factor: " + loadFactor); }
//读取元素个数,并检查不能小于0 int size = s.readInt(); if (size < 0) { throw new InvalidObjectException("Illegal size: " + size); } //根据元素个数重新设置容量,这是为了保证map有足够的容量融安所有元素,防止无意义的扩容 capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f), HashMap.MAXIMUM_CAPACITY);
//再次检查,HashMap中构建hash桶数组是在第一个元素被添加时候才构建的,所以在构建之前检查 //调用HashMap.tableSizeFor来计算实际分配的大小 //检查Map.Entry[]类,因为是最接近公共类型实际创建的内容 SharedSecrets.getJavaOISAccess() .checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));
//创建Map,检查是不是LinkedHashSet类型 map = (((HashSet<?>)this) instanceof LinkedHashSet ? new LinkedHashMap<E,Object>(capacity, loadFactor) : new HashMap<E,Object>(capacity, loadFactor));
//读取所有元素放到map中 for (int i=0; i<size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); } } |
构造器:
/** * 空构造器,空的HashSet,底层HashMap实例的默认初始化容量是16,加载因子是0.75 */ public HashSet() { map = new HashMap<>(); } /** * 包含指定collection中的元素的新set */ public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } /** * 新的空set,其底层是HashMap,所以指定初始化容量和加载因子 * @param initialCapacity 初始化容量 * @param loadFactor */ public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } /** * 空set,底层是HashMap,默认的加载因子是0.75 * @param initialCapacity 指定容量 */ public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); }
/** * 这个构造器的修饰符是defualt,这个构造器主要是给子类LinkedHashSet用的 * @param initialCapacity * @param loadFactor * @param dummy 这个是用于LinkedList */ HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); } |
其他常用方法:
/** * 迭代器 */ public Iterator<E> iterator() { return map.keySet().iterator(); }
/** * 集合元素个数 */ public int size() { return map.size(); }
/** * 集合是否为空 */ public boolean isEmpty() { return map.isEmpty(); }
/** * 是否包含某个元素 */ public boolean contains(Object o) { return map.containsKey(o); }
/** * 新增元素 */ public boolean add(E e) { return map.put(e, PRESENT)==null; }
/** * 删除元素 */ public boolean remove(Object o) { return map.remove(o)==PRESENT; }
/** * 清空 */ public void clear() { map.clear(); }
/** * 可分割迭代器,主要用于多线程并行迭代处理使用的 */ public Spliterator<E> spliterator() { return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0); } |
以上的其他操作元素的方法中,会发现都是调用Map的方法,所以底层就是Map,如迭代器就是调用Map.key.iterator,而这个就是在AbstractMap源码解读时所提到的keySet()方法内定义的迭代器。
注:在Set接口中也没有发现有get()方法,在HashSet也发现没有get()方法,但是有迭代方法,这说明了要访问Set元素,只能通过迭代器来遍历元素,不能随机访问,因为元素存储时就是hashCode计算位置。