Java-Collection源碼分析(九)——WeakHashMap

  WeakHashMap 繼承於AbstractMap,實現了Map接口。和HashMap一樣,WeakHashMap也是一個哈希表,它存儲的內容也是鍵值對(key-value)映射,而且鍵和值都可以是null。

WeakHashMap中的每個關鍵對象間接存儲爲弱引用的引用。因此,只有在地圖的內部和外部的弱引用之後,密鑰纔會被垃圾回收器清除。

一、WeakHashMap的垃圾回收機制

1.1 WeakHashMap的Entry定義

該散列表中的條目擴展了WeakReference,使用其主ref字段作爲關鍵字。

    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;
        Entry(Object key, V value,
              ReferenceQueue<Object> queue, //定義了一個引用隊列
              int hash, Entry<K,V> next) {
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }
        public K getKey() {
            return (K) WeakHashMap.unmaskNull(get());
        }
        public V getValue() {
            return value;
        }
        public V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            K k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                V v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }
        public int hashCode() {
            K k = getKey();
            V v = getValue();
            return Objects.hashCode(k) ^ Objects.hashCode(v);
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

} public String toString() { return getKey() + "=" + getValue(); } }

1.2 回收機制

定義了一個queue,保存的是“已被GC清除”的“弱引用的鍵”。

弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。

    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

從表中刪除陳舊的條目。即刪除與引用隊列中重合的元素

    private void expungeStaleEntries() {
        for (Object x; (x = queue.poll()) != null; ) {
            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) {
                    Entry<K,V> next = p.next;
                    if (p == e) {
                        if (prev == e)
                            table[i] = next;
                        else
                            prev.next = next;
                        //陳舊的條目可能由HashIterator使用
                        e.value = null; //幫助垃圾回收
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }
三、主要方法
    //返回指定鍵映射到的值,如果此映射不包含鍵的映射,則返回null。
    public V get(Object key) {
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] tab = getTable();
        int index = indexFor(h, tab.length);
        Entry<K,V> e = tab[index];
        while (e != null) {	//遍歷hash表
            if (e.hash == h && eq(k, e.get()))  //判斷hash值和key值
                return e.value;
            e = e.next;
        }
        return null;
    }
    //如果此映射包含指定鍵的映射,則返回true。
    public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }
    Entry<K,V> getEntry(Object key) {
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] tab = getTable();
        int index = indexFor(h, tab.length);
        Entry<K,V> e = tab[index];
        while (e != null && !(e.hash == h && eq(k, e.get())))
            e = e.next;
        return e;
    }
    //將指定的值與此映射中的指定鍵相關聯。
    public V put(K key, V value) {
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] tab = getTable();
        int i = indexFor(h, tab.length);
        //遍歷表得到key,用新value替換舊的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;
            }
        }
        modCount++;
        Entry<K,V> e = tab[i];
        //新的Entry添加到哈希表
        tab[i] = new Entry<>(k, value, queue, h, e);
        if (++size >= threshold)
            resize(tab.length * 2);
        return null;
    }
    //將該map的內容重新設置爲具有較大容量的新陣列。 當此map中的鍵數達到其閾值時,將自動調用此方法。 如果當前容量爲MAXIMUM_CAPACITY,則此方法不會調整映射大小,而是將閾值設置爲Integer.MAX_VALUE。 這具有防止未來呼叫的效果。
    void resize(int newCapacity) {
        Entry<K,V>[] oldTable = getTable();
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        Entry<K,V>[] newTable = newTable(newCapacity);
        transfer(oldTable, newTable);
        table = newTable;
        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) {
        for (int j = 0; j < src.length; ++j) {
            Entry<K,V> e = src[j];
            src[j] = null;	//將src所有的內容設置爲null,幫助垃圾回收
            while (e != null) {
                Entry<K,V> next = e.next;
                Object key = e.get();
                if (key == null) {
                    e.next = null;  // Help GC
                    e.value = null; //  "   "
                    size--;
                } else {
                    int i = indexFor(e.hash, dest.length);
                    e.next = dest[i];
                    dest[i] = e;
                }
                e = next;
            }
        }
    }
    //將指定map的所有映射覆制到此地圖。這些映射將替換此映射對當前指定映射中的任何鍵的任何映射。
    public void putAll(Map<? extends K, ? extends V> m) {
        int numKeysToBeAdded = m.size();
        if (numKeysToBeAdded == 0)
            return;
        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);
        }

        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }
    //如果存在,則從此弱散列映射中刪除key的映射。
    public V remove(Object key) {
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] tab = getTable();
        int i = indexFor(h, tab.length);
        Entry<K,V> prev = tab[i];
        Entry<K,V> e = prev;
        //與刪除單向鏈表中的節點原理相同
        while (e != null) {
            Entry<K,V> next = e.next;
            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;
    }

    boolean removeMapping(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Entry<K,V>[] tab = getTable();
        Map.Entry<?,?> entry = (Map.Entry<?,?>)o;
        Object k = maskNull(entry.getKey());
        int h = hash(k);
        int i = indexFor(h, tab.length);
        Entry<K,V> prev = tab[i];
        Entry<K,V> e = prev;
        while (e != null) {
            Entry<K,V> next = e.next;
            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;
    }
    //從此map中刪除所有的映射。
    public void clear() {
        while (queue.poll() != null)
        modCount++;
        Arrays.fill(table, null);
        size = 0;
        while (queue.poll() != null)
            ;
    }
    //如果此映射將一個或多個鍵映射到指定的值,則返回true。
    public boolean containsValue(Object value) {
        if (value==null)
            return 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 (value.equals(e.value))
                    return true;
        return false;
    }
    //是否包含null值
    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;
    }



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