Java集合容器系列08-HashSet

1、HashSet的介紹

    HashSet是一個依賴於HashMap的Set接口實現,容器的元素存儲和操做都是基於內部的一個HashMap實例實現,由於這個緣由,它不保證Set中元素的迭代順序特別是不保證該順序的恆久不變,容許插入null元素。該類能夠爲基本的集合操做提供穩定的性能保證,這些基本操做包括add、remove、contains和size,假定哈希函數正確地將元素分佈在底層HashMap的槽中,那麼對此HashSet進行迭代所需的時間與元素的個數和底層HashMap的槽的個數成正比的,因此迭代性能很重要的話,就不要將初始容量設置得過高(或者負載因子設置得過低)。注意HashSet不是線程安全的容器,若是有多個線程訪問該容器,且至少有一個線程對容器作告終構性修改,那麼它就必須在外部保證同步,這一般是經過對操做該容器的代碼塊加鎖實現的,若是沒有則可使用Collections.synchronizedSet在包裝它做爲一個線程安全的容器使用。HashSet的iterator返回的迭代器對象Iterator是fail-fast(快速失敗)的,如何理解,即在該迭代器建立以後任什麼時候間對該容器作告終構性修改(除了基於iterator.remve方法刪除容器元素以外)都將致使迭代器遍歷時拋出ConcurrentModificationException異常,這種快速失敗行爲沒法絕對保證,所以依賴於這個特性編寫應用程序是錯誤的。java

2、HashSet的數據結構

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    //底層的HashMap實例,這個對象是HashSet的核心
    private transient HashMap<E,Object> map;
    //HashSet的元素其實就是保存在HashMap實例的KeySet集合裏,key/value鍵值對中的value就直接保存它這個固定的對象
    private static final Object PRESENT = new Object();
}

    HashSet繼承自AbstractSet類,該類實現了一些集合和Set基本操做方法,實現了Set接口在這裏幾乎起的相似代碼註釋的功能,由於AbstractSet自己已經實現了Set接口,經過繼承它HashSet也間接實現了Set接口,此外HashSet也實現了Clonable接口支持對象的clone()方法拷貝,實現了java.io.Serializable代表該類也支持序列化安全

3、HashSet源碼分析

1 - 構造方法

/**
     * 構造函數,底層的HashMap實例默認的初始容量16,負載因子0.75 
     */
    public HashSet() {
        map = new HashMap<>();
    }

    /**
     * 構造函數,在初始化實例時將指定容器的全部元素插入容器中,底層HashMap實例默認負載因子0.75,初始化容量取基於負載 
     * 因子計算的容器容量和默認初始容量的較大值
     */
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

    /**
     * 構造函數,爲底層HashMap指定初始容量和加載因子
     */
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

    /**
     * 構造函數,爲底層HashMap指定初始容量,,加載因子默認0.75
     */
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

    /**
     * 構造函數,該方法屬於包私有的方法僅被LinkedHashSet使用,dummy參數僅僅只是爲了和第三個構造方法(底層是HashMap 
     * 實例)作區分
     */
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

 

2 - boolean isEmpty()方法 - 判空

public boolean isEmpty() {
        return map.isEmpty();
    }

    很簡單容器的元素都保存在底層的HashMap實例中,因此直接判斷底層HashMap實例是否爲空。數據結構

3 - boolean add(E e) - 添加元素

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

    分析方法源碼可知HashSet添加元素實際也是調用底層HashMap的put方法,將元素保存在底層HashMap實例的鍵值對key/value的key中函數

 

4 - boolean remove(Object o)

public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

    同樣也是直接調用底層HashMap的remove方法刪除元素o源碼分析

5 - 其餘成員方法

//返回容器迭代器,無序
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

    /**
     * 返回容器保存的元素個數
     */
    public int size() {
        return map.size();
    }

    /**
     * 返回容器是否爲空
     */
    public boolean isEmpty() {
        return map.isEmpty();
    }

    /**
     * 若是容器包含對象o返回true不然返回false
     */
    public boolean contains(Object o) {
        return map.containsKey(o);
    }


    public Object clone() {
        try {
            HashSet<E> newSet = (HashSet<E>) super.clone();
            newSet.map = (HashMap<E, Object>) map.clone();
            return newSet;
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }

    /**
     * 序列化時調用保存對象狀態
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out any hidden serialization magic
        s.defaultWriteObject();

        // Write out HashMap capacity and load factor
        s.writeInt(map.capacity());
        s.writeFloat(map.loadFactor());

        // Write out size
        s.writeInt(map.size());

        // Write out all elements in the proper order.
        for (E e : map.keySet())
            s.writeObject(e);
    }

    /**
     * 反序列時調用
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();

        // Read capacity and verify non-negative.
        int capacity = s.readInt();
        if (capacity < 0) {
            throw new InvalidObjectException("Illegal capacity: " +
                                             capacity);
        }

        // Read load factor and verify positive and non NaN.
        float loadFactor = s.readFloat();
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        }

        // Read size and verify non-negative.
        int size = s.readInt();
        if (size < 0) {
            throw new InvalidObjectException("Illegal size: " +
                                             size);
        }

        capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                HashMap.MAXIMUM_CAPACITY);

        SharedSecrets.getJavaOISAccess()
                     .checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));

        // Create backing HashMap
        map = (((HashSet<?>)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            @SuppressWarnings("unchecked")
                E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }

    /**
     * 返回一個延遲綁定的迭代器
     */
    public Spliterator<E> spliterator() {
        return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
    }

 

6 - HashSet源碼總結

    HashSet底層實現依賴於對象內部的一個HashMap實例,容器元素最終是以鍵值對key的形式保存在底層HashMap實例中,HashSet容器的全部操做實際操做的都是底層的HashMap實例性能

相關文章
相關標籤/搜索