Java中的Map【九】WeakHashMap類

       所使用的jdk版本爲1.8.0_172版本,先看一下 WeakHashMap<K,V> 在JDK中Map的UML類圖中的主要繼承實現關係:

概述

       WeakHashMap 是基於 弱引用(WeakReference)類型實現的。 在 WeakHashMap 中,對鍵K的引用是弱引用類型,當某個鍵不再正常使用,比如只被弱引用關聯時,我們知道此時垃圾回收器會回收該鍵,此時WeakHashMap將自動移除該鍵對應的映射條目。null 值和 null 鍵都被支持。該類具有與 HashMap 類相似的性能特徵,並具有相同的效能參數初始容量 和加載因子。WeakHashMap 的實現是不同步的,即線程不安全的。

示例如下:

public static void main(String[] args) {
        //weakHashMap存儲 學生-分數 映射
        WeakHashMap<Student, Integer> weakHashMap = new WeakHashMap<>();
        //小明和小華對象分別有強引用關聯:xiaoMing 和 xiaoHua;小亮直接new的對象,沒有強引用關係
        Student xiaoMing = new Student("小明", 9);
        Student xiaoHua = new Student("小華",8);
        weakHashMap.put(xiaoMing, 100);
        weakHashMap.put(xiaoHua, 86);
        weakHashMap.put(new Student("小亮",9), 59);

        System.out.println("GC 前:");
        System.out.println(weakHashMap);

        System.gc();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("GC 後:");
        System.out.println(weakHashMap);
    }

    public static class Student {
        private String name;
        private Integer age;

        public Student(String name, Integer age){
            this.name = name;
            this.age = age;
        }

        public String getName(){
            return this.name;
        }

        public Integer getAge(){
            return this.age;
        }

        @Override
        public int hashCode() {
            return name.hashCode() ^ age.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj instanceof Student) {
                return name.equals(((Student) obj).getName()) && age.equals(((Student) obj).getAge());
            }
            return false;
        }

        @Override
        public String toString() {
            return "{name:"+name+",age:"+age+"}";
        }
    }

程序運行結果:

GC 前:
{{name:小明,age:9}=100, {name:小華,age:8}=86, {name:小亮,age:9}=59}
GC 後:
{{name:小明,age:9}=100, {name:小華,age:8}=86}

       new 出的小亮學生對象,沒有強引用關聯,只有weakHashMap 對它的弱引用,當GC時,小亮對象會被回收,weakHashMap 會回收小亮對象鍵對應的映射關係。

數據結構      

public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> {

      WeakHashMap 底層又其內部類節點數組Entry<K,V>[] table  和Entry<K,V> 鏈表實現的,另有隊列ReferenceQueue queue 保存GC回收鍵的弱引用對象,用來清除映射。

      Entry<K,V> 類繼承了WeakReference類,與其它Map實現最大的不同,就是把對鍵key的引用,由強引用關聯改成了弱引用關聯:

    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) {
            //給鍵 Key 對象創建一個新的的弱引用關聯,非強引用關聯
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }

實現原理

put(K key, V value) 方法

public V put(K key, V value) {
        //如果key是null的話,使用一個空Object對象代替
        Object k = maskNull(key);
        //擾動函數計算hash值
        int h = hash(k);
        //getTable()方法中清除過時的key(已被GC回收的key)對應的映射關係
        Entry<K,V>[] tab = getTable();
        int i = indexFor(h, tab.length);

        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];
        tab[i] = new Entry<>(k, value, queue, h, e);
        if (++size >= threshold)
            //擴容操作
            resize(tab.length * 2);
        return null;
    }

getTable() 方法

private Entry<K,V>[] getTable() {
        // 清除過時的key對應的映射
        expungeStaleEntries();
        return table;
    }

expungeStaleEntries() 方法

  /**
     * Expunges stale entries from the table.
     * 刪除被GC回收鍵對應的映射關係
     */
    private void expungeStaleEntries() {
        //從ReferenceQueue隊列中獲取被回收的鍵
        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;
                //遍歷鏈表結構,找到被回收的鍵x,單鏈表刪除節點
                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
                        // value引用設爲null,幫助GC回收
                        e.value = null; // Help GC
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }

  expungeStaleEntries() 方法在擴容resize()、get(Object key)、size()等方法中都有調用。

注意點:

WeakHashMap 中的每個鍵對象間接地存儲爲一個弱引用的指示對象。因此,不管是在映射內還是在映射之外,只有在垃圾回收器清除某個鍵的弱引用之後,該鍵纔會自動移除。

實現注意事項:WeakHashMap 中的值對象由普通的強引用保持。因此應該小心謹慎,確保值對象不會直接或間接地強引用其自身的鍵,因爲這會阻止鍵的丟棄。注意,值對象可以通過 WeakHashMap 本身間接引用其對應的鍵;這就是說,某個值對象可能強引用某個其他的鍵對象,而與該鍵對象相關聯的值對象轉而強引用第一個值對象的鍵。處理此問題的一種方法是,在插入前將值自身包裝在 WeakReferences 中,如:m.put(key, new WeakReference(value)),然後,分別用 get 進行解包。

 

題外:

分析以下程序的輸出結果是什麼效果:

public static void test1() {
        String four = "four", five = "five";
        WeakHashMap<String,Integer> weakHashMap1 = new WeakHashMap<>();
        weakHashMap1.put(four, 4);
        weakHashMap1.put(five, 5);
        weakHashMap1.put("six", 6);

        WeakHashMap<String,Integer> weakHashMap2 = new WeakHashMap<>();
        weakHashMap2.put(four, 4);
        weakHashMap2.put(five, 5);
        weakHashMap2.put(new String("six"), 6);

        System.out.println("GC 前:");
        System.out.println(weakHashMap1);
        System.out.println(weakHashMap2);

        System.gc();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("GC 後:");
        System.out.println(weakHashMap1);
        System.out.println(weakHashMap2);
    }

程序運行結果:

GC 前:
{six=6, four=4, five=5}
{six=6, four=4, five=5}
GC 後:
{six=6, four=4, five=5}
{four=4, five=5}

 

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