java集合(三)Set集合之HashSet詳解


 

①:實現了Serializable接口,表明它支持序列化。
②:實現了Cloneable接口,表明它支持克隆,可以調用超類的clone()方法進行淺拷貝。
③繼承了AbstractSet抽象類,和ArrayList和LinkedList一樣,在他們的抽象父類中,都提供了equals()方法和hashCode()方法。它們自身並不實現這兩個方法,(但是ArrayList和LinkedList的equals()實現不同。你可以看我的關於ArrayList這一塊的源碼解析)這就意味着諸如和HashSet一樣繼承自AbstractSet抽象類的TreeSet、LinkedHashSet等,他們只要元素的個數和集合中元素相同,即使他們是AbstractSet不同的子類,他們equals()相互比較的后的結果仍然是true。下面給出的代碼是JDK中的equals()代碼:
從JDK源碼可以看出,底層並沒有使用我們常規認為的利用hashcode()方法求的值進行比較,而是通過調用AbstractCollection的containsAll()方法,如果他們中元素完全相同(與順序無關),則他們的equals()方法的比較結果就為true。

    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Set))
            return false;
        Collection<?> c = (Collection<?>) o;
        //必須保證元素的個數相等。
        if (c.size() != size())
            return false;
        try {
        //調用了AbstractCollection的方法。
            return containsAll(c);
        } catch (ClassCastException unused)   {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }
    }
    public boolean containsAll(Collection<?> c) {
    //只需要逐個判斷集合是否包含其中的元素。
        for (Object e : c)
            if (!contains(e))
                return false;
        return true;
    }

④實現了Set接口。

老規矩還是先把總結放到上面:
①HashSet內部通過使用HashMap的鍵來存儲集合中的元素,而且內部的HashMap的所有值
都是null。(因為在為HashSet添加元素的時候,內部HashMap的值都是PRESENT),而PRESENT在實例域的地方直接初始化了,而且不允許改變。
②HashSet對外提供的所有方法內部都是通過HashMap操作完成的,所以,要真正理解HashSet的實現,只需要把HashMap的原理理解即可。我也對HashMap做了分析。傳送門:
③所以,如果你對HashMap很熟悉的話,學習HastSet的源碼輕而易舉!

1:首先我們先看看HashSet的有哪些實例域:(是不是少了很多,而且還簡單(⊙o⊙)…)

    //序列化ID
    static final long serialVersionUID = -5024744406713321676L;
    //底層使用了HashMap存儲數據。
    private transient HashMap<E,Object> map;
    //用來填充底層數據結構HashMap中的value,因為HashSet只用key存儲數據。
    private static final Object PRESENT = new Object();

2:老規矩,還是先看一下它們的構造方法:
構造器的實現基本都是使用HashMap

//其實只是實例化了HashMap (⊙o⊙)…(不懂的童鞋,可以看我的另一篇關於HashMap的源碼解析)
    public HashSet() {
        map = new HashMap<>();
    }
//還是關於
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
    //調用的AbstractCollection中的方法。然后調用HashSet的add()方法把集合中的
    //所有元素添加到了集合中。(因為)
    public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }
    //底層並不支持直接在AbstractCollection類中調用add()方法,而是調用add()方法
    //的自身實現。
public boolean add(E e) {
        throw new UnsupportedOperationException();
    }
//初始化HashSet仍然是關於HashMap的知識。
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
//初始化HashSet仍然是關於HashMap的知識。
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }
//這次初始化底層使用了LinkedHashMap實現。
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

3:添加新的元素:

//底層仍然利用了HashMap鍵進行了元素的添加。
//在HashMap的put()方法中,該方法的返回值是對應HashMap中鍵值對中的值,而值總是PRESENT,
//該PRESENT一直都是private static final Object PRESENT = new Object();
//PRESENT只是初始化了,並不能改變,所以PRESENT的值一直為null。
//所以只要插入成功了,put()方法返回的值總是null。
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

4:刪除一個元素:

 

//該方法底層實現了仍然使用了map的remove()方法。
//map的remove()方法的返回的是被刪除鍵對應的值。(在HashSet的底層HashMap中的所有
//鍵值對的值都是PRESENT)
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

5:清空方法

    public void clear() {
        map.clear();
    }

6:克隆方法(淺克隆)
底層仍然使用了Object的clone()方法,得到的Object對象,並把它強制轉化為HashSet<E>,然后把它的底層的HashMap也克隆一份(調用的HashMap的clone()方法),並把它賦值給newSet,最后返回newSet即可。

    @SuppressWarnings("unchecked")
    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);
        }
    }

7:是否包含某個元素

底層仍然使用了HashMap的containsKey()方法(因為HashSet只用到了HashMap的鍵)同樣的話重復了好多遍 ……….(艹皿艹 )

    public boolean contains(Object o) {
        return map.containsKey(o);
    }

8:判斷是否為空
仍然調用HashMap的方法。

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

9:生成一個迭代器

仍然使用了HashMap的鍵的迭代器

    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

10: 統計HashSet中包含元素的個數

還是調用HashMap的實現。

    public int size() {
        return map.size();
    }

11:把HashSet寫入流中(也就是序列化)
在HashSet序列化實現中,仍然是把HashMap中的屬性寫入流中。(因為HashSet中真正存儲數據的數據結構是HashMap。)

    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
        //把HashMap的容量寫入流中。
        s.writeInt(map.capacity());
        //把HashMap的裝載因子寫入流中。
        s.writeFloat(map.loadFactor());
        //把HashMap中鍵值對的個數寫入流中。
        s.writeInt(map.size());

        // 按正確的順序把集合中的所有元素寫入流中。
        for (E e : map.keySet())
            s.writeObject(e);
    }

12:從流中讀取數據,組裝HashSet(反序列化)

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);
        }
        // Set the capacity according to the size and load factor ensuring that
        // the HashMap is at least 25% full but clamping to maximum capacity.
        capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                HashMap.MAXIMUM_CAPACITY);

        //HashMap中構建哈希桶數組是在第一個元素被添加的時候才構建,所以在構建之前檢查它,
        // 調用HashMap.tableSizeFor來計算實際分配的大小,
        // 檢查Map.Entry []類,因為它是最接近的公共類型實際創建的內容。

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

        //創建HashMap。
        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);
        }
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM