Java JDK1.8(八) - HashSet源碼解析

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佔位符:用來存儲到Mapvalue

 */

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);

    }

 

    //讀取負載因子,不能爲0NaN

    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計算位置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章