java集合的底層原理(Map的底層原理(WeakHashMap) 四)

一、概念相關

      WeakHashMap  ,從名字上看,  直接翻譯就知道,這是一種弱的HashMap,那這個弱字表面看起來有點不好理解,其實這是跟對象引用相關的,先熟悉了對象引用原理,再來理解這個弱字就很容易了,如不明白對象引用原理,大家可以看看這篇博文 Java中的對象、對象引用及對象引用分類

 弄明白了對象引用原理,就明白我們的WeakHashMap是基於弱引用的,也就是說只要垃圾回收機制一開啓,就直接開始了掃蕩,看見了就清除。

那我們爲什麼需要 WeakHashMap 呢?

WeakHashMap正是由於使用的是弱引用,因此它的對象可能被隨時回收。更直觀的說,當使用 WeakHashMap 時,即使沒有刪除任何元素,它的尺寸、get方法也可能不一樣。比如:

(1)調用兩次size()方法返回不同的值;第一次爲10,第二次就爲8了。

(2)兩次調用isEmpty()方法,第一次返回false,第二次返回true;

(3)兩次調用containsKey()方法,第一次返回true,第二次返回false;

(4)兩次調用get()方法,第一次返回一個value,第二次返回null;

 咋一想,這種飄忽不定的東西好像沒什麼用,試想一下,你準備使用WeakHashMap保存一些數據,寫着寫着都沒了,那還保存個啥呀。

不過有一種場景,最喜歡這種飄忽不定、一言不合就刪除的東西。那就是緩存。在緩存場景下,由於內存是有限的,不能緩存所有對象,因此就需要一定的刪除機制,淘汰掉一些對象。

 

二、工作原理

1、WeakHashMap爲什麼具有弱引用的特點:隨時被回收對象

這個問題就比較簡單了,我們的目的主要是驗證。WeakHashMap是基於弱引用的,肯定就具有了弱引用的性質。我們去他的部分源碼中看一下:

 

public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> {
    /**
     * The default initial capacity -- MUST be a power of two.
     */
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    private static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * Reference queue for cleared WeakEntries
     */
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    Entry<K,V>[] table;
    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);
    }

    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;

        /**
         * Creates new entry.
         */
        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;
        }
}

 

從這裏我們可以看到其內部的Entry繼承了WeakReference,也就是弱引用,所以就具有了弱引用的特點。不過還要注意一點,那就是ReferenceQueue,他的作用是GC會清理掉對象之後,引用對象會被放到ReferenceQueue中。

2、WeakHashMap中的Entry被GC後,WeakHashMap是如何將其移除的?

意思是某一個Entry突然被垃圾回收了,這之後WeakHashMap肯定就不能保留這個Entry了,那他是如何將其移除的呢?

WeakHashMap內部有一個expungeStaleEntries函數,在這個函數內部實現移除其內部不用的entry從而達到的自動釋放內存的目的。因此我們每次訪問WeakHashMap的時候,都會調用這個expungeStaleEntries函數清理一遍。這也就是爲什麼前兩次調用WeakHashMap的size()方法有可能不一樣的原因。我們可以看看是如何實現的:

  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;
                        // Must not null out e.next;
                        // stale entries may be in use by a HashIterator
                        e.value = null; // Help GC
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }

首先GC每次清理掉一個對象之後,引用對象會被放到ReferenceQueue中。然後遍歷這個queue進行刪除即可。

當然。WeakHashMap的增刪改查操作都會直接或者間接的調用expungeStaleEntries()方法,達到及時清除過期entry的目的。

 

三、WeakHashMap的使用

 

1、緩存中使用

Car car = new Car(22000,"silver");
WeakReference<Car> weakCar = new WeakReference<Car>(car);

    HashMap和WeakHashMap的區別也在於此,HashMap的key是對實際對象的強引用。

  弱引用(WeakReference)的特性是:當gc線程發現某個對象只有弱引用指向它,那麼就會將其銷燬並回收內存。WeakReference也會被加入到引用隊列queue中。

2、不合理的使用

   不要使用基礎類型作爲WeakHashMap的key

緩存的使用案例太多了,這裏舉一個WeakHashMap使用不規範的例子。

   

Map<Object, Object> objectMap = new WeakHashMap<Object, Object>();
for (int i = 0; i < 1000; i++) {
    objectMap.put(i, new Object());
    System.gc();
    System.out.println("Map size :" + objectMap.size());
}

 

我們期待得到1和0的輸出,但是實際上看到的是許多128!有趣的是,並非所有的東西都是GC感興趣的。在上面的情景中,int i會被轉化爲封裝類型Integer,但是Integer保留了-128到127的緩存,因此哪些Key <= 127的Entry將不會進行自動回收。所以不要使用原始類型(及其對應的包裝類型)。

 

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