Java集合(七)--WeakHashMap簡析

WeakHashMap是一個(gè)帶有弱鍵的Map坷剧,即當(dāng)某個(gè)鍵不再正常使用的時(shí)候,這個(gè)鍵就會(huì)被移除喊暖,它所對(duì)應(yīng)的鍵值對(duì)也就被移除了惫企。該類支持空鍵和空值,具有與HashMap相似的性能特征陵叽,有與初始容量和負(fù)載因子狞尔。WeakHashMap是非同步的。

WeakHashMap的定義及說明

定義如下:

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

WeakHashMap的定義還是比較簡單的巩掺,就是繼承了AbstractMap偏序,實(shí)現(xiàn)了Map。即擁有Map基本的屬性和方法胖替。

構(gòu)造方法

構(gòu)造方法如下:

//最大的容量研儒,如果具有參數(shù)的任一構(gòu)造函數(shù)隱式指定較高值,則使用最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;

//默認(rèn)的初始容量独令,必須是2的次方
private static final int DEFAULT_INITIAL_CAPACITY = 16;

//默認(rèn)的加載因子
private static final float DEFAULT_LOAD_FACTOR = 0.75f;

//數(shù)組類型端朵,實(shí)際是一個(gè)單鏈表,數(shù)組長度必須是2的次方
Entry<K,V>[] table;

//閾值(容量*loadFaactor)
private int threshold;

//加載因子
private final float loadFactor;

//WeakHashMap中包含的鍵值的數(shù)量
private int size;

//使用給定的初始容量和給定的加載因子構(gòu)造一個(gè)新的空WeakHashMap记焊。
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);
        //計(jì)算初始容量(找到大于initialCapacity的最小的2的次方)
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;
        //新建一個(gè)空數(shù)組
        table = newTable(capacity);
        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
    }

//使用給定的初始容量和默認(rèn)加載因子(0.75)構(gòu)造一個(gè)新的空WeakHashMap逸月。
public WeakHashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

//使用默認(rèn)初始容量(16)和加載因子(0.75)構(gòu)造一個(gè)新的空WeakHashMap
public WeakHashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

//使用與指定映射相同的映射構(gòu)造一個(gè)新的WeakHashMap。 使用默認(rèn)加載因子(0.75)創(chuàng)建WeakHashMap遍膜,且初始容量要足以保存指定映射中的映射碗硬。
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);
    }

有沒有一種熟悉的感覺?不錯(cuò)瓢颅,就是HashMap恩尾。可以看到它與HashMap相類似挽懦,都有容量翰意、加載因子、閾值以及都是單鏈表結(jié)構(gòu)信柿。怎么看出是一個(gè)單鏈表冀偶?看一下Entry就了然了:

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        //值
        V value;
        //hash值
        final int hash;
        //下個(gè)節(jié)點(diǎn)
        Entry<K,V> next;

        Entry(Object key, V value,
              ReferenceQueue<Object> queue,
              int hash, Entry<K,V> next) {
             //通過WeakReference調(diào)用Reference的方法,使key注冊到queue中渔嚷,并且进鸠,key為弱引用
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }

}

其中ReferenceQueue是一個(gè)引用隊(duì)列,在檢測到可到達(dá)性更改后形病,垃圾回收器將已注冊的引用對(duì)象添加到隊(duì)列中客年,主要是用于監(jiān)聽Reference所指向的對(duì)象是否已經(jīng)被垃圾回收。通過"super(key, queue)"方法及其調(diào)用方法漠吻,使key成為一個(gè)弱引用對(duì)象量瓜,并將其注冊到queue中,以便在key被GC的時(shí)候可以添加到queue中去途乃。

源碼簡析

我們還是從put()方法入手绍傲,分析一下它的實(shí)現(xiàn)原理:

//已清除的Entries的引用隊(duì)列
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

public V put(K key, V value) {
        //判斷key是否為空
        Object k = maskNull(key);
        //獲取key經(jīng)過處理后的hashCode
        int h = hash(k);
        //獲取當(dāng)前(清除了被GC了的key所對(duì)應(yīng)的映射) 的表
        Entry<K,V>[] tab = getTable();
        //返回哈希碼h在數(shù)組中對(duì)應(yīng)的索引
        int i = indexFor(h, tab.length);
        //獲取i對(duì)應(yīng)的索引位置的鏈表的首節(jié)點(diǎn)并遍歷鏈表
        for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
            //判斷hash值是否相等
            if (h == e.hash && eq(k, e.get())) {
                //獲取當(dāng)前位置的舊值
                V oldValue = e.value;
                //新舊值不相等,則新值覆蓋舊值
                if (value != oldValue)
                    e.value = value;
                //返回舊值耍共,結(jié)束后面的操作
                return oldValue;
            }
        }
       
        //修改次數(shù)加1
        modCount++;
        //將新元素添加到數(shù)組中
        Entry<K,V> e = tab[i];
        tab[i] = new Entry<>(k, value, queue, h, e);
        //調(diào)整數(shù)組大小
        if (++size >= threshold)
            resize(tab.length * 2);
        return null;
    }

//對(duì)key的hashCode做處理
final int hash(Object k) {
        //獲取k的hashCode
        int h = k.hashCode();
        //hashCode混淆
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

//首次刪除過時(shí)條目后返回表
private Entry<K,V>[] getTable() {
        expungeStaleEntries();
        return table;
    }

//從表中清除過時(shí)的條目
private void expungeStaleEntries() {
        //遍歷queue中的key(已被GC)
        for (Object x; (x = queue.poll()) != null; ) {
            synchronized (queue) {
                @SuppressWarnings("unchecked")
                //取出queue中的當(dāng)前位置的元素    
                Entry<K,V> e = (Entry<K,V>) x;
                //獲取該key在數(shù)組中的所對(duì)應(yīng)的索引
                int i = indexFor(e.hash, table.length);
                //將當(dāng)前key賦值與prev
                Entry<K,V> prev = table[i];
                Entry<K,V> p = prev;
                //遍歷索引i對(duì)應(yīng)的鏈表
                while (p != null) {
                    //獲取p的下一個(gè)元素并賦值給next
                    Entry<K,V> next = p.next;
                    //判斷p是否等于e(除了第一次輪詢以外烫饼,其他情況下p = prev.next())
                    if (p == e) {
                        //判斷prev是否等于e
                        if (prev == e)
                            //p和prev都等于e,則直接將數(shù)組索引為i的鏈表的首節(jié)點(diǎn)替換為next
                            table[i] = next;
                        else
                            //只有p等于e划提,則改變prev的下一個(gè)元素的指向枫弟,將其變?yōu)閜的下一個(gè)元素,即刪除p
                            prev.next = next;
                        //不得將e.next歸零; 舊的條目可能被HashIterator使用
                        //置為空以方便GC回收
                        e.value = null;
                        //數(shù)組長度減一
                        size--;
                        break;
                    }
                    //重新賦值鹏往,以便再次循環(huán)
                    prev = p;
                    p = next;
                }
            }
        }
    }


//返回哈希碼h對(duì)應(yīng)的索引
private static int indexFor(int h, int length) {
        return h & (length-1);
    }

//調(diào)整數(shù)組大小
void resize(int newCapacity) {
        //獲取當(dāng)前table數(shù)組
        Entry<K,V>[] oldTable = getTable();
        //獲取當(dāng)前數(shù)組長度
        int oldCapacity = oldTable.length;
        //判斷數(shù)組長度是否大于最大值
        if (oldCapacity == MAXIMUM_CAPACITY) {
            //將閾值置為最大值
            threshold = Integer.MAX_VALUE;
            return;
        }
        //使用新的數(shù)組長度新建一個(gè)數(shù)組
        Entry<K,V>[] newTable = newTable(newCapacity);
        //將舊數(shù)組的所有元素移入新數(shù)組
        transfer(oldTable, newTable);
        //將table指向新數(shù)組
        table = newTable;

        //判斷數(shù)組長度是否大于等于閾值的1/2
        if (size >= threshold / 2) {
            //重新計(jì)算閾值
            threshold = (int)(newCapacity * loadFactor);
        } else {
            //從表中清除過時(shí)的條目
            expungeStaleEntries();
            //將新元素移入舊的數(shù)組
            transfer(newTable, oldTable);
            //將table指向舊數(shù)組
            table = oldTable;
        }
    }

//將所有條目從src傳輸?shù)絛est
private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
        for (int j = 0; j < src.length; ++j) {
            //獲取src指定位置的元素
            Entry<K,V> e = src[j];
            src[j] = null;
            //遍歷指定索引下的鏈表
            while (e != null) {
                Entry<K,V> next = e.next;
                //獲取key
                Object key = e.get();
                if (key == null) {
                    //key為null則將其next和value置為空淡诗,且數(shù)組長度減一
                    e.next = null;
                    e.value = null;
                    size--;
                } else {
                    //獲取e在dest數(shù)組中所對(duì)應(yīng)的索引,并賦值
                    int i = indexFor(e.hash, dest.length);
                    e.next = dest[i];
                    dest[i] = e;
                }
                //賦值并重新開始循環(huán)
                e = next;
            }
        }
    }

從上面的分析中我們可以看出伊履,WeakHashMap每次put一個(gè)元素韩容,都會(huì)先刪除table中不再正常使用的元素。然后再判斷當(dāng)前已有元素中的key是否有與要添加元素的key相同的唐瀑,相同則覆蓋其值群凶,不同則將新元素添加如數(shù)組中。在這個(gè)過程中不再正常使用的鍵的原理為:當(dāng)WeakHashMap中的鍵(弱引用)被GC回收時(shí)哄辣,該鍵會(huì)被添加到queue中请梢。然后赠尾,在put過程中會(huì)執(zhí)行expungeStaleEntries()方法,該方法會(huì)遍歷queue中的key并刪除WeakHashMap中與其key對(duì)應(yīng)的鍵值對(duì)毅弧。

在expungeStaleEntries()方法中气嫁,有一個(gè)queue.poll()方法,它是ReferenceQueue類中的方法够坐,具體如下:


//ReferenceQueue的隊(duì)首
private Reference<? extends T> head = null;

//ReferenceQueue的隊(duì)尾
private Reference<? extends T> tail = null;

//判斷隊(duì)列中是否為空寸宵,如果不為空,則取出鏈表中head位置的元素
public Reference<? extends T> poll() {
        synchronized (lock) {
            if (head == null)
                return null;
            return reallyPollLocked();
        }
    }

//Reference.queueNext將設(shè)置為sQueueNextUnenqueued元咙,以指示何時(shí)將引用放入隊(duì)列并從隊(duì)列中刪除梯影。
private static final Reference sQueueNextUnenqueued = new PhantomReference(null, null);

private Reference<? extends T> reallyPollLocked() {
        
        if (head != null) {
            Reference<? extends T> r = head;
            if (head == tail) {
                tail = null;
                head = null;
            } else {
                head = head.queueNext;
            }
            //更新queueNext以指示引用已入隊(duì),但現(xiàn)在已從隊(duì)列中刪除庶香。
            r.queueNext = sQueueNextUnenqueued;
            return r;
        }

        return null;
    }

作用就是取出queue中的key甲棍,以方便從數(shù)組中刪除。

好了脉课,本文到此結(jié)束救军。

感覺自己博客寫的好亂!L攘恪唱遭!后期要整理一下。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呈驶,一起剝皮案震驚了整個(gè)濱河市拷泽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌袖瞻,老刑警劉巖司致,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異聋迎,居然都是意外死亡脂矫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門霉晕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庭再,“玉大人,你說我怎么就攤上這事牺堰≈羟幔” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵伟葫,是天一觀的道長恨搓。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么斧抱? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任常拓,我火速辦了婚禮,結(jié)果婚禮上夺姑,老公的妹妹穿的比我還像新娘墩邀。我一直安慰自己掌猛,他們只是感情好盏浙,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荔茬,像睡著了一般废膘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上慕蔚,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天丐黄,我揣著相機(jī)與錄音,去河邊找鬼孔飒。 笑死灌闺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的坏瞄。 我是一名探鬼主播桂对,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鸠匀!你這毒婦竟也來了蕉斜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤缀棍,失蹤者是張志新(化名)和其女友劉穎宅此,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爬范,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡父腕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了青瀑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片璧亮。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖狱窘,靈堂內(nèi)的尸體忽然破棺而出杜顺,到底是詐尸還是另有隱情,我是刑警寧澤蘸炸,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布躬络,位于F島的核電站,受9級(jí)特大地震影響搭儒,放射性物質(zhì)發(fā)生泄漏穷当。R本人自食惡果不足惜提茁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望馁菜。 院中可真熱鬧茴扁,春花似錦、人聲如沸汪疮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽智嚷。三九已至卖丸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盏道,已是汗流浹背稍浆。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留猜嘱,地道東北人衅枫。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像朗伶,于是被迫代替她去往敵國和親弦撩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349