JDK11-WeakHashMap集合

介紹

 WeakHashMap 繼承於AbstractMap,實現了Map接口。
    和HashMap一樣,WeakHashMap 也是一個散列表,它存儲的內容也是鍵值對(key-value)映射,而且鍵和值都可以是null
   不過WeakHashMap的鍵是“弱鍵”。在 WeakHashMap 中,當某個鍵不再正常使用時,會被從WeakHashMap中被自動移除。更精確地說,對於一個給定的鍵,其映射的存在並不阻止垃圾回收器對該鍵的丟棄,這就使該鍵成爲可終止的,被終止,然後被回收。某個鍵被終止時,它對應的鍵值對也就從映射中有效地移除了。
    這個“弱鍵”的原理呢?大致上就是,通過WeakReference和ReferenceQueue實現的。 WeakHashMap的key是“弱鍵”,即是WeakReference類型的;ReferenceQueue是一個隊列,它會保存被GC回收的“弱鍵”。

成員變量

 /**
     * 默認初始容量是16,必須是2的冪
     */
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    /**
     * 最大容量(必須是2的冪且小於2的30次方,傳入容量過大將被這個值替換
     */
    private static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默認加載因子
     */
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 存儲數據的Entry數組,長度是2的冪。
     */
    Entry<K,V>[] table;

    /**
     * WeakHashMap的大小,它是WeakHashMap保存的鍵值對的數量
     */
    private int size;

    /**
     * WeakHashMap的閾值,用於判斷是否需要調整WeakHashMap的容量(threshold = 容量*加載因子).
     */
    private int threshold;

    /**
     *加載因子實際大小
     */
    private final float loadFactor;

    /**
     * queue保存的是“已被GC清除”的“弱引用的鍵”。
     */
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

    /**
     * WeakHashMap被改變的次數
     */
    int modCount;

構造函數

//指定容量大小和加載因子的構造函數  
public WeakHashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Initial Capacity: "+
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;

        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load factor: "+
                                               loadFactor);
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;
        table = newTable(capacity);
        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
    }

    /**
     指定容量大小的構造函數
     */
    public WeakHashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     默認構造函數
     */
    public WeakHashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    /**
     包含子map的構造函數
     */
    public WeakHashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                DEFAULT_INITIAL_CAPACITY),
             DEFAULT_LOAD_FACTOR);
        putAll(m);
    }

核心函數

//根據key獲取value
public V get(Object key) {
       //檢查key爲null則返回NULL_KEY對象
        Object k = maskNull(key);
       //取key的hashCode
        int h = hash(k);
       //通過getTable獲取數組,去除所有需要被回收的Entry
        Entry<K,V>[] tab = getTable();
       //計算出該key的下標索引
        int index = indexFor(h, tab.length);
       //獲取該索引位置的鏈表頭
        Entry<K,V> e = tab[index];
       ////遍歷鏈表,根據hashCode值與equals比較雙重判斷獲取value
        while (e != null) {
            if (e.hash == h && eq(k, e.get()))
                return e.value;
            e = e.next;
        }
        return null;
    }
//判斷WeakHashMap中是否包含key
public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }
//根據key獲取Entry實體
 Entry<K,V> getEntry(Object key) {
//檢查key爲null則返回NULL_KEY對象
        Object k = maskNull(key);
//獲取key的hashCode
        int h = hash(k);
        //通過getTable獲取數組,去除所有需要被回收的Entry
        Entry<K,V>[] tab = getTable();
//計算出該key的下標索引
        int index = indexFor(h, tab.length);
//獲取該索引位置的鏈表頭
        Entry<K,V> e = tab[index];
 //遍歷鏈表,根據hashCode值與equals比較雙重判斷獲取Entry
        while (e != null && !(e.hash == h && eq(k, e.get())))
            e = e.next;
        return e;
    }

//將k-v添加到映射關係中
public V put(K key, V value) {
        //判斷key是否爲null,如果爲null,new Object個對象
        Object k = maskNull(key);
        //獲取hash值
        int h = hash(k);
        //通過getTable獲取數據,去除所有需要被回收的Entry
        Entry<K,V>[] tab = getTable();
        //獲取到key的下標索引
        int i = indexFor(h, tab.length);
        //遍歷鏈表,根據hashCode值與equals比較雙重判斷key是否存在,存在則將value替換,並直接返回舊值
        for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
            if (h == e.hash && eq(k, e.get())) {
                V oldValue = e.value;
                if (value != oldValue)
                    e.value = value;
                return oldValue;
            }
        }
        //若key不存在,則將該節點插入鏈表頭,這裏和HashMap不太一樣,HashMap是尾部追加
        modCount++;
        Entry<K,V> e = tab[i];
        tab[i] = new Entry<>(k, value, queue, h, e);
        //添加元素後的WeakHashMap大小如果大於等於擴容閾值,則擴容1倍
        if (++size >= threshold)
            resize(tab.length * 2);
        return null;
    }

//刪除所有被GC回收的Entry
private void expungeStaleEntries() {
        for (Object x; (x = queue.poll()) != null; ) {//存在對象被GC,那麼就需要移除map中對應的數據
            synchronized (queue) {//線程同步,鎖定隊列
                @SuppressWarnings("unchecked")
                    Entry<K,V> e = (Entry<K,V>) x;
                int i = indexFor(e.hash, table.length);//定位到節點位置

                Entry<K,V> prev = table[i];
                Entry<K,V> p = prev;
                while (p != null) {//如果p節點存在
                    Entry<K,V> next = p.next;//定義next節點指向p的下個節點
                    if (p == e) {//如果p就是當前節點
                        if (prev == e)
                            table[i] = next;//桶中第一個數據就是移除的,直接把第二個節點放到節點的位置
                        else
                            prev.next = next;//把上一個節點的下個節點指向p後面的節點
                        // Must not null out e.next;
                        // stale entries may be in use by a HashIterator
                        e.value = null; // Help GC
                        size--;//減少WeakHashMap的大小
                        break;//結束
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }
//核心擴容方法
void resize(int newCapacity) {
        //獲取原Entry數組,獲取之前先刪除所有需要移除的Entry
        Entry<K,V>[] oldTable = getTable();
        //獲取原Entry數組的容量
        int oldCapacity = oldTable.length;
        //如果原數組的容量已經達到最大值2^30,則停止擴容並防止再次擴容
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        //實例化新Entry數組
        Entry<K,V>[] newTable = newTable(newCapacity);
        //將原數組中的元素全部放入新數組
        transfer(oldTable, newTable);
        //將底層數組替換爲新數組
        table = newTable;

        /*
         * If ignoring null elements and processing ref queue caused massive
         * shrinkage, then restore old table.  This should be rare, but avoids
         * unbounded expansion of garbage-filled tables.
         */
        //如果忽略空元素並處理ref隊列導致大量收縮,則恢復舊錶。這應該是很少見的,但是避免了垃圾填充表的無限制擴展
        if (size >= threshold / 2) {
            threshold = (int)(newCapacity * loadFactor);
        } else {
            expungeStaleEntries();
            transfer(newTable, oldTable);
            table = oldTable;
        }
    }
//將src數組中的元素全部放入dest數組
private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
         //遍歷src數組
        for (int j = 0; j < src.length; ++j) {
            //依次將src數組中的元素取出
            Entry<K,V> e = src[j];
            //取出後將src中去除引用
            src[j] = null;
            //遍歷鏈表
            while (e != null) {
                //取當前節點的下一節點next
                Entry<K,V> next = e.next;
                //獲取弱鍵
                Object key = e.get();
                 //弱鍵爲空則說明已被GC回收,將該節點清空,方便GC回收該Entry
                if (key == null) {
                    e.next = null;  // Help GC
                    e.value = null; //  "   "
                    size--;
                } else {
                     //不爲空說明沒有被回收,重新計算在dest數組中的索引
                    int i = indexFor(e.hash, dest.length);
                     //將該節點插入dest索引i處鏈表頭
                    e.next = dest[i];
                    dest[i] = e;
                }
                e = next;
            }
        }
    }
//將一個map中的所有元素全部放入WeakHashMap
public void putAll(Map<? extends K, ? extends V> m) {
         //取傳入map的大小
        int numKeysToBeAdded = m.size();
          //若傳入的是一個空map,直接return
        if (numKeysToBeAdded == 0)
            return;

        /*
         * Expand the map if the map if the number of mappings to be added
         * is greater than or equal to threshold.  This is conservative; the
         * obvious condition is (m.size() + size) >= threshold, but this
         * condition could result in a map with twice the appropriate capacity,
         * if the keys to be added overlap with the keys already in this map.
         * By using the conservative calculation, we subject ourself
         * to at most one extra resize.
         */
        //如果要添加映射的數量大於或等於閾值,這是保守的;明顯的條件是(m.size() + size) >= 閾值,
        //但是這個條件可能導致map的容量是適當容量的兩倍,如果要添加的鍵與此映射中已經存在的鍵重疊。
        //通過使用保守的計算,我們最多可額外調整一次大小。
        if (numKeysToBeAdded > threshold) {
            int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
            if (targetCapacity > MAXIMUM_CAPACITY)
                targetCapacity = MAXIMUM_CAPACITY;
            int newCapacity = table.length;
            while (newCapacity < targetCapacity)
                newCapacity <<= 1;
            if (newCapacity > table.length)
                resize(newCapacity);
        }
         //遍歷map,依次將元素存入WeakHashMap中
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }

//根據key將Entry移除WeakHashMap
public V remove(Object key) {
        //檢查key爲null則返回NULL_KEY對象
        Object k = maskNull(key);
        //取key的hashCode
        int h = hash(k);
        //獲取Entry數組,獲取之前先刪除所有需要被移除的Entry
        Entry<K,V>[] tab = getTable();
        //計算出該key的下標索引
        int i = indexFor(h, tab.length);
        //取索引i處的Entry,用來遍歷鏈表時存放上個節點
        Entry<K,V> prev = tab[i];
        //用來遍歷鏈表時存放當前節點
        Entry<K,V> e = prev;
        //遍歷鏈表
        while (e != null) {
             //取當前節點的下一節點next
            Entry<K,V> next = e.next;
             //根據hashCode值與equals比較雙重判斷key是否相同,存在則移除該節點並返回該節點value
            if (h == e.hash && eq(k, e.get())) {
                modCount++;
                size--;
                if (prev == e)
                    tab[i] = next;
                else
                    prev.next = next;
                return e.value;
            }
           //不存在繼續遍歷
            prev = e;
            e = next;
        }

        return null;
    }

//移除WeakHashMap中指定Entry
boolean removeMapping(Object o) {
         //判斷對象o是否爲Entry實例,不是則直接return
        if (!(o instanceof Map.Entry))
            return false;
        //獲取Entry數組,獲取之前先刪除所有需要被移除的Entry
        Entry<K,V>[] tab = getTable();
        //將對象o強轉爲Entry實體
        Map.Entry<?,?> entry = (Map.Entry<?,?>)o;
        //檢查key爲null則返回NULL_KEY對象
        Object k = maskNull(entry.getKey());
        //取key的hashCode
        int h = hash(k);
        //計算出該key的下標索引
        int i = indexFor(h, tab.length);
         //取索引i處的Entry,用來遍歷鏈表時存放上個節點
        Entry<K,V> prev = tab[i];
        //用來遍歷鏈表時存放當前節點
        Entry<K,V> e = prev;

        //遍歷鏈表
        while (e != null) {
            //取當前節點的下一節點next
            Entry<K,V> next = e.next;
            //根據hashCode值與equals比較雙重判斷Entry是否爲要移除的對象,若是則移除並返回true
            if (h == e.hash && e.equals(entry)) {
                modCount++;
                size--;
                if (prev == e)
                    tab[i] = next;
                else
                    prev.next = next;
                return true;
            }
            //若不是則繼續遍歷
            prev = e;
            e = next;
        }

        return false;
    }
//清空WeakHashMap
 public void clear() {
        // clear out ref queue. We don't need to expunge entries
        // since table is getting cleared.
        //清空queue,將queue中的元素一個個取出
        while (queue.poll() != null)
            ;

        modCount++;
        //通過Arrays.fill把底層數組所有元素全部清空
        Arrays.fill(table, null);
        size = 0;

        // Allocation of array may have caused GC, which may have caused
        // additional entries to go stale.  Removing these entries from the
        // reference queue will make them eligible for reclamation.
        //將數組清空後可能引發了GC導致queue中又添加了元素,再次清空
        while (queue.poll() != null)
            ;
    }

//判斷WeakHashMap中是否包含value
 public boolean containsValue(Object value) {
         //如果value爲空,調用是否包含空value邏輯
        if (value==null)
            return containsNullValue();
         //獲取Entry數組,獲取之前先刪除所有需要被移除的Entry
        Entry<K,V>[] tab = getTable();
         //遍歷
        for (int i = tab.length; i-- > 0;)
         //遍歷鏈表
            for (Entry<K,V> e = tab[i]; e != null; e = e.next)
                 //equals比較value值,相同則返回true
                if (value.equals(e.value))
                    return true;
        return false;
    }
//判斷是否包含空value
 private boolean containsNullValue() {
        Entry<K,V>[] tab = getTable();
        for (int i = tab.length; i-- > 0;)
            for (Entry<K,V> e = tab[i]; e != null; e = e.next)
                if (e.value==null)
                    return true;
        return false;
    }
//返回WeakHashMap中的所有key的set集合
 public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }
//返回WeakHashMap中的所有value的集合
public Collection<V> values() {
        Collection<V> vs = values;
        if (vs == null) {
            vs = new Values();
            values = vs;
        }
        return vs;
    }
//返回WeakHashMap中的所有Entry的set集合
public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es = entrySet;
        return es != null ? es : (entrySet = new EntrySet());
    }
expungeStaleEntries源碼總結:

①:循環遍歷引用隊列(queue), 如果發現某個對象被GC了,那麼就開始處理。
②:如果被處理的這個節點是頭節點,那麼直接把該節點的下個節點放到頭節點,然後幫助GC去除value的引用,接着把WeakHashMap的大小減1。
③:如果被處理的這個節點不是頭結點,那麼就需要把這個節點的上個節點中的next指針直接指向當前節點的下個節點。意思就是a->b->c,這個時候要移除b,那麼就變成a->c。然後幫助GC去除value的引用,接着把WeakHashMap的大小減1。
 

 

 

 

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