Java集合(十)--LinkedHashMap簡(jiǎn)析

漏掉一個(gè)Map输拇,補(bǔ)一下

LinkedHashMap是HashMap的子類,此實(shí)現(xiàn)與HashMap的不同之處在于它維護(hù)了一個(gè)貫穿其所有條目的雙向鏈表贤斜。 此鏈表定義迭代排序策吠,通常是鍵插入映射的順序(插入順序)。 請(qǐng)注意瘩绒,如果將鍵重新插入Map猴抹,則不會(huì)影響插入順序。

定義及說明

定義如下:

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{}

它繼承于HashMap草讶,底層使用哈希表和鏈表來維護(hù)集合洽糟。

構(gòu)造方法為:

//此鏈表哈希映射的迭代排序方法:true表示訪問順序炉菲,false表示插入順序堕战。
final boolean accessOrder;

//使用指定的初始容量和加載因子構(gòu)造一個(gè)空的插入排序的LinkedHashMap實(shí)例
public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

//使用指定的初始容量和默認(rèn)加載因子(0.75)構(gòu)造一個(gè)空的插入排序的LinkedHashMap實(shí)例
public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

//使用默認(rèn)初始容量(16)和加載因子(0.75)構(gòu)造一個(gè)空的插入順序LinkedHashMap實(shí)例坤溃。
public LinkedHashMap() {
        super();
        accessOrder = false;
    }

//構(gòu)造一個(gè)插入有序的LinkedHashMap實(shí)例,其具有與指定映射相同的映射嘱丢。 LinkedHashMap實(shí)例使用默認(rèn)加載因子(0.75)和足以保存指定映射中的映射的初始容量創(chuàng)建薪介。
public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

//使用指定的初始容量,加載因子和排序模式構(gòu)造一個(gè)空的LinkedHashMap實(shí)例越驻。
public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

我們可以看到汁政,LinkedHashMap是調(diào)用父類的構(gòu)造函數(shù)做初始化。其構(gòu)造函數(shù)中的accessOrder屬性決定了迭代順序缀旁,true表示訪問順序记劈,false表示插入順序。默認(rèn)使用插入順序并巍,即使用通過put或其類似方法插入的順序進(jìn)行排序目木。

源碼及簡(jiǎn)析

按照慣例應(yīng)該是分析put()方法了,但是懊渡,很尷尬刽射,LinkedHashMap并沒有重寫put()方法,也就是說剃执,它的put()方法跟HashMap是相同的誓禁。但是,他重寫了putVal()方法中調(diào)用的afterNodeAccess()和afterNodeInsertion()方法肾档,我們看一下:

//LinkedHashMap自己的Entry
static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
        LinkedHashMapEntry<K,V> before, after;
        LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

//雙向鏈表的頭部
transient LinkedHashMapEntry<K,V> head;

//雙向鏈表的尾部
transient LinkedHashMapEntry<K,V> tail;


//將節(jié)點(diǎn)移動(dòng)到最后
void afterNodeAccess(Node<K,V> e) {
        LinkedHashMapEntry<K,V> last;
        //如果是按訪問順序排序摹恰,且e不是鏈表的尾部
        if (accessOrder && (last = tail) != e) {
            //將e節(jié)點(diǎn)賦值給p,將e的前節(jié)點(diǎn)賦值給b阁最,將e的后節(jié)點(diǎn)賦值給a戒祠,將tail賦值給last
            LinkedHashMapEntry<K,V> p =
                (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
            //將p的后一個(gè)節(jié)點(diǎn)置為空
            p.after = null;
            //如果p為鏈表頭部
            if (b == null)
                //將a置為鏈表頭部
                head = a;
            else
                //否則,將a置為b的后一個(gè)元素
                b.after = a;
            //如果p不是鏈表的尾部
            if (a != null)
                //將a的上一個(gè)元素置為b
                a.before = b;
            else
                //否則速种,將b賦值給last
                last = b;
            //當(dāng)前鏈表如果為空
            if (last == null)
                //將p置為鏈表的頭部
                head = p;
            else {
                //否則姜盈,將p置為last的下一個(gè)元素
                p.before = last;
                last.after = p;
            }
            //將p置為鏈表的尾部
            tail = p;
            ++modCount;
        }
    }

//在HashMap中傳過來的evict為true
void afterNodeInsertion(boolean evict) {
        LinkedHashMapEntry<K,V> first;
        //判斷是否刪除過時(shí)的條目
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            //調(diào)用HashMap的方法
            removeNode(hash(key), key, null, false, true);
        }
    }

//如果此映射應(yīng)刪除其最舊條目,則返回true配阵。它允許映射通過刪除過時(shí)條目來減少內(nèi)存消耗馏颂。
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        //糟老頭子壞的很,它直接返回了falseF灏>壤!
        return false;
    }

通過上面分析瘫拣,我們發(fā)現(xiàn)亿絮,如果是按照訪問順序排序,則經(jīng)過一系列的判斷和賦值后,將新元素插入到鏈表的尾部派昧。也就是說黔姜,如果按照訪問的順序排序,則排序靠后的蒂萎,就是最近插入的元素秆吵。要注意,這個(gè)排序跟在Map中的存儲(chǔ)順序沒有關(guān)系五慈,只是在雙向鏈表中的順序纳寂。我們還發(fā)現(xiàn)在LinkedHashMapEntry類內(nèi)部,出現(xiàn)了before和after兩個(gè)屬性泻拦,而根據(jù)在afterNodeAccess()方法中的分析毙芜,他們分別指向元素的上一個(gè)和下一個(gè)節(jié)點(diǎn)。那么争拐,由此得出爷肝,在LinkedHashMap中的鏈表是一個(gè)雙向鏈表。

刪除條目時(shí)陆错,調(diào)用了HashMap的remove()方法灯抛,LinkedHashMap重寫了其中調(diào)用的afterNodeRemoval(node)方法(在removeNode方法中調(diào)用),我們現(xiàn)在來看一下:

void afterNodeRemoval(Node<K,V> e) {
        //將e的值賦予p音瓷,將p的上個(gè)節(jié)點(diǎn)的值賦予b对嚼,p的下個(gè)節(jié)點(diǎn)的值賦予a
        LinkedHashMapEntry<K,V> p =
            (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
        //將p前、后節(jié)點(diǎn)置為空
        p.before = p.after = null;
        //如果p為鏈表頭部
        if (b == null)
            //將a設(shè)置為鏈表頭部
            head = a;
        else
            //否則绳慎,將b的下個(gè)元素由變?yōu)閍
            b.after = a;
        //如果p為鏈表尾部
        if (a == null)
            //將b設(shè)為鏈表尾部
            tail = b;
        else
            //否則纵竖,將b設(shè)置為a的上個(gè)元素
            a.before = b;
    }

可以看到,通過對(duì)指定元素e前后節(jié)點(diǎn)b杏愤、a的設(shè)置靡砌,使得其b、a的指向不再指向p珊楼,以將其刪除通殃。

接下來,我們看一下它的get()方法:

public V get(Object key) {
        //初始化一個(gè)節(jié)點(diǎn)對(duì)象
        Node<K,V> e;
        //判斷key所對(duì)應(yīng)的節(jié)點(diǎn)是否為空厕宗,不為空則獲取key所對(duì)應(yīng)的節(jié)點(diǎn)值
        if ((e = getNode(hash(key), key)) == null)
            return null;
        //判斷排序方式
        if (accessOrder)
            //如果是訪問順序画舌,則重排序
            afterNodeAccess(e);
        return e.value;
    }


//重排序(或是調(diào)用了put()方法之后,為節(jié)點(diǎn)設(shè)置前已慢、后位置的元素)
void afterNodeAccess(Node<K,V> e) { 
        LinkedHashMapEntry<K,V> last;
        //判斷排序方式是按訪問順序排序曲聂,且e節(jié)點(diǎn)不是雙向鏈表的尾部
        if (accessOrder && (last = tail) != e) {
            //將e節(jié)點(diǎn)賦值給p,將e的前節(jié)點(diǎn)賦值給b佑惠,將e的后節(jié)點(diǎn)賦值給a
            LinkedHashMapEntry<K,V> p =
                (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
            //將p的后一個(gè)節(jié)點(diǎn)置為空
            p.after = null;
            if (b == null)
                //如果b為空朋腋,證明e為首節(jié)點(diǎn)齐疙,則將e的下個(gè)節(jié)點(diǎn)置為首節(jié)點(diǎn)
                head = a;
            else
                //如果b不為空,則將a置為其下個(gè)節(jié)點(diǎn)旭咽,替代e的位置
                b.after = a;
            if (a != null)
                //如果a不為空剂碴,則將a的上一個(gè)節(jié)點(diǎn)置為b,替代e的位置
                a.before = b;
            else
                //如果a為空轻专,則將b的值賦值給last
                last = b;
            if (last == null)
                //如果last為空(即tail為空,且b為空察蹲,說明只有p一個(gè)元素)请垛,則將p置為首節(jié)點(diǎn)
                head = p;
            else {
                //如果last不為空,則將p的前節(jié)點(diǎn)置為last洽议,last的后節(jié)點(diǎn)置為p
                p.before = last;
                last.after = p;
            }
            /將p置為尾節(jié)點(diǎn)
            tail = p;
            ++modCount;
        }
    }

可以發(fā)現(xiàn)宗收,我們把get方法訪問過的元素放到鏈表的尾部,而通過上面的分析亚兄,我們發(fā)現(xiàn)混稽,如果主動(dòng)刪除元素,則從鏈表的頭部開始审胚。這樣匈勋,就形成了一個(gè)簡(jiǎn)單的LRU。不過膳叨,由于其removeEldestEntry()方法直接返回了false洽洁,所以,如果我們要用它做LRU菲嘴,就需要自己寫一個(gè)類繼承LinkedHashMap饿自,并重寫removeEldestEntry()方法。比如龄坪,我們要求Map的長(zhǎng)度在100以內(nèi):

private static final int MAX_ENTRIES = 100;

@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
     return size() > MAX_ENTRIES;
}

小結(jié)

1昭雌、LinkedHashMap繼承于HashMap,是一個(gè)基于HashMap和雙向鏈表實(shí)現(xiàn)的Map健田。

2烛卧、LinedHashMap是有順序的,分別為按照插入順序排序和按照訪問順序排序妓局,默認(rèn)為按照插入順序排序唱星。

3、LinkedHashMap是非同步的跟磨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末间聊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子抵拘,更是在濱河造成了極大的恐慌哎榴,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異尚蝌,居然都是意外死亡迎变,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門飘言,熙熙樓的掌柜王于貴愁眉苦臉地迎上來衣形,“玉大人,你說我怎么就攤上這事姿鸿∽晃猓” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵苛预,是天一觀的道長(zhǎng)句狼。 經(jīng)常有香客問我,道長(zhǎng)热某,這世上最難降的妖魔是什么腻菇? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮昔馋,結(jié)果婚禮上筹吐,老公的妹妹穿的比我還像新娘。我一直安慰自己秘遏,他們只是感情好骏令,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著垄提,像睡著了一般榔袋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铡俐,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天凰兑,我揣著相機(jī)與錄音,去河邊找鬼审丘。 笑死吏够,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的滩报。 我是一名探鬼主播锅知,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼脓钾!你這毒婦竟也來了售睹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤可训,失蹤者是張志新(化名)和其女友劉穎昌妹,沒想到半個(gè)月后捶枢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡飞崖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年烂叔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片固歪。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蒜鸡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出牢裳,到底是詐尸還是另有隱情逢防,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布贰健,位于F島的核電站,受9級(jí)特大地震影響恬汁,放射性物質(zhì)發(fā)生泄漏伶椿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一氓侧、第九天 我趴在偏房一處隱蔽的房頂上張望脊另。 院中可真熱鬧,春花似錦约巷、人聲如沸偎痛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)踩麦。三九已至,卻和暖如春氓癌,著一層夾襖步出監(jiān)牢的瞬間谓谦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工贪婉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留反粥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓疲迂,卻偏偏與公主長(zhǎng)得像才顿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尤蒿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Collection & Map Collection 子類有 List 和 Set List --> Array...
    任教主來也閱讀 3,164評(píng)論 1 9
  • 1 前言 LinkedHashMap繼承于HashMap郑气,如果對(duì)HashMap原理還不清楚的同學(xué),請(qǐng)先看上一篇:圖...
    唐江旭閱讀 197,015評(píng)論 37 326
  • Java集合類可用于存儲(chǔ)數(shù)量不等的對(duì)象,并可以實(shí)現(xiàn)常用的數(shù)據(jù)結(jié)構(gòu)如棧,隊(duì)列等,Java集合還可以用于保存具有映射關(guān)...
    小徐andorid閱讀 1,942評(píng)論 0 13
  • 郁孤臺(tái)下清江水腰池,中間多少行人淚竣贪。西北望長(zhǎng)安军洼,可憐無數(shù)山,青山遮不住演怎,畢竟東流去匕争。
    加百列v閱讀 53評(píng)論 0 0
  • ———分享趣味詩(shī) 上班族往下看 秋 似酒 味醇厚 ...
    淡似閑云閱讀 424評(píng)論 3 15