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); } |
以上的其他操作元素的方法中,會發現都是調用HashMap的方法,所以底層就是HashMap,如迭代器就是調用HashMap.key.iterator,而這個就是在AbstractMap源碼解讀時所提到的keySet()方法內定義的迭代器,進而也可以推斷出HashSet就是數組+單鏈表/紅黑樹。
注:在Set接口中也沒有發現有get()方法,在HashSet也發現沒有get()方法,但是有迭代方法,這說明了要訪問Set元素,只能通過迭代器來遍歷元素,不能隨機訪問,因爲元素存儲時就是hashCode計算位置。