吃透Java集合系列十一:LinkedHashMap

文章首發(fā)csdn博客地址:https://blog.csdn.net/u013277209?viewmode=contents

一:概要

HashMap是Java集合中的重要成員仍翰,也是Map族中我們最為常用的一種梦皮,但是HashMap是無序的,也就是說励翼,迭代HashMap所得到的元素順序并不是它們最初放置到HashMap的順序抡秆。

HashMap的這一缺點往往會造成諸多不便大溜,因為在有些場景中差油,我們確需要用到一個可以保持插入順序的Map泳炉。慶幸的是憾筏,JDK為我們解決了這個問題,它為HashMap提供了一個子類 —— LinkedHashMap花鹅。雖然LinkedHashMap增加了時間和空間上的開銷氧腰,但是它通過維護一個額外的雙向鏈表保證了迭代順序。

該迭代順序可以是插入順序刨肃,也可以是訪問順序古拴。因此,根據(jù)鏈表中元素的順序可以將LinkedHashMap分為:保持插入順序的LinkedHashMap和保持訪問順序的LinkedHashMap真友,其中LinkedHashMap的默認實現(xiàn)是按插入順序排序的黄痪。

其實LinkedHashMap就是 HashMap+雙向鏈表 來實現(xiàn)的,就是將所有Node節(jié)點鏈入一個雙向鏈表的HashMap盔然。在LinkedHashMapMap中桅打,所有put進來的Node都保存在哈希表中是嗜,但由于它又額外定義了一個以head為頭結(jié)點的雙向鏈表,因此對于每次put進來Node挺尾,除了將其保存到哈希表中對應(yīng)的位置上之外鹅搪,還會將其插入到雙向鏈表的尾部。


LinkedHashMap結(jié)構(gòu)示意圖

二潦嘶、源碼分析

類信息
LinkedHashMap繼承于HashMap涩嚣,并在此基礎(chǔ)上擴展了雙向鏈表。

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

成員變量

與HashMap相比掂僵,LinkedHashMap增加了兩個屬性用于保證迭代順序航厚,分別是 雙向鏈表頭結(jié)點head尾結(jié)點tail 和 標志位accessOrder (值為true時,表示按照訪問順序迭代锰蓬;值為false時幔睬,表示按照插入順序迭代)。

    //頭結(jié)點
    transient LinkedHashMap.Entry<K,V> head;
    //尾結(jié)點
    transient LinkedHashMap.Entry<K,V> tail;
    //true表示按照訪問順序迭代芹扭,false時表示按照插入順序
    final boolean accessOrder;

元素類
LinkedHashMap采用的hash算法和HashMap相同麻顶,但是它重新定義了Entry。LinkedHashMap中的Entry增加了兩個指針 before 和 after舱卡,它們分別用于維護雙向鏈接列表辅肾。特別需要注意的是,next用于維護HashMap各個桶中Entry的連接順序轮锥,before矫钓、after用于維護Entry插入的先后順序的,源代碼如下:

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

構(gòu)造函數(shù)
LinkedHashMap 一共提供了五個構(gòu)造函數(shù)舍杜,它們都是在HashMap的構(gòu)造函數(shù)的基礎(chǔ)上實現(xiàn)的新娜,除了默認空參數(shù)構(gòu)造方法,下面這個構(gòu)造函數(shù)包含了大部分其他構(gòu)造方法使用的參數(shù)既绩。

public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);// 調(diào)用HashMap對應(yīng)的構(gòu)造函數(shù)
        this.accessOrder = accessOrder;//指定迭代順序方式
    }

put方法
LinkedHashMap沒有對 put(key,vlaue) 方法進行任何直接的修改概龄,完全繼承了HashMap的 put(Key,Value) 方法,HashMap中put源碼如下:

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //tab為空則創(chuàng)建
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //計算index饲握,并對null做處理
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //節(jié)點key存在私杜,直接覆蓋value
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //判斷該鏈為紅黑樹
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //該鏈為鏈表
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //鏈表長度大于8轉(zhuǎn)換為紅黑樹進行處理
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                     // key已經(jīng)存在直接覆蓋value
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //超過最大容量 就擴容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

在LinkedHashMap中,它對newNode方法進行了重寫救欧。下面我們對比地看一下LinkedHashMap 和HashMap的newNode方法的具體實現(xiàn):

//LinkedHashMap
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }

//HashMap
```java
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        return new Node<>(hash, key, value, next);
    }

可見在LinkedHashMap中向哈希表中插入新Node的同時歪今,還會通過linkNodeLast方法將其鏈入到雙向鏈表中,相比HashMap而言颜矿,LinkedHashMap在向哈希表添加一個鍵值對的同時,也會將其鏈入到它所維護的雙向鏈表中嫉晶,以便設(shè)定迭代順序骑疆。

get方法
相對于LinkedHashMap的存儲而言田篇,讀取就顯得比較簡單了。LinkedHashMap中重寫了HashMap中的get方法箍铭,源碼如下:

public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

在LinkedHashMap的get方法中泊柬,通過HashMap中的getNode方法獲取Node對象。注意這里的afterNodeAccess方法诈火,如果鏈表中元素的排序規(guī)則是按照插入的先后順序排序的話(當accessOrder為false時)兽赁,該方法什么也不做;如果鏈表中元素的排序規(guī)則是按照訪問的先后順序排序的話(當accessOrder為true時)冷守,則將e移到鏈表的末尾處刀崖。

void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        // 判斷迭代策略,并且當前節(jié)點不是尾節(jié)點
        if (accessOrder && (last = tail) != e) {
            // 記錄當前節(jié)點拍摇,并獲取前后節(jié)點
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            // 把當前節(jié)點的 after 節(jié)點置 null
            p.after = null;
            // 如果當前節(jié)點是頭節(jié)點亮钦,把后一個節(jié)點置為頭節(jié)點
            if (b == null)
                head = a;
            // 把當前節(jié)點的前后節(jié)點相連
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            // 把當前節(jié)點置為尾節(jié)點并記錄
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

擴容機制
由于LinkedHashMap并沒有重寫HashMap的擴容,所以其擴容是和HashMap保持一致的充活,具體可以參見吃透Java集合系列九:HashMap

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜂莉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子混卵,更是在濱河造成了極大的恐慌映穗,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幕随,死亡現(xiàn)場離奇詭異蚁滋,居然都是意外死亡,警方通過查閱死者的電腦和手機合陵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門枢赔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拥知,你說我怎么就攤上這事踏拜。” “怎么了低剔?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵速梗,是天一觀的道長。 經(jīng)常有香客問我襟齿,道長姻锁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任猜欺,我火速辦了婚禮位隶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘开皿。我一直安慰自己涧黄,他們只是感情好篮昧,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著笋妥,像睡著了一般懊昨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上春宣,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天酵颁,我揣著相機與錄音,去河邊找鬼月帝。 笑死躏惋,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的嫁赏。 我是一名探鬼主播其掂,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼潦蝇!你這毒婦竟也來了款熬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤攘乒,失蹤者是張志新(化名)和其女友劉穎贤牛,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體则酝,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡殉簸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了沽讹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片般卑。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖爽雄,靈堂內(nèi)的尸體忽然破棺而出蝠检,到底是詐尸還是另有隱情,我是刑警寧澤挚瘟,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布叹谁,位于F島的核電站,受9級特大地震影響乘盖,放射性物質(zhì)發(fā)生泄漏焰檩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一订框、第九天 我趴在偏房一處隱蔽的房頂上張望析苫。 院中可真熱鬧,春花似錦、人聲如沸衩侥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽顿乒。三九已至,卻和暖如春泽谨,著一層夾襖步出監(jiān)牢的瞬間璧榄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工吧雹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留骨杂,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓雄卷,卻偏偏與公主長得像搓蚪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子丁鹉,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

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

  • 臥槽妒潭,剛吃完晚飯手機突然卡住變磚了,向下音量鍵+關(guān)機鍵 麻麻再也不用擔心我的手機變磚了
  • 我在遠處凝望揣钦,看燈光 嵌在無數(shù)的窗口上 陌生的人在屋里收拾過往 留下一條幽暗的長廊 那夢中故鄉(xiāng)的月亮 曾失落過誰的...
    同敬閱讀 541評論 6 29
  • 用條件格式扮靚報表 一雳灾、基本用法: 1.條件格式--選中-突出顯示單元格規(guī)則 顏色自定義 取消—清除 2.突出單元...
    若嫻兒馬閱讀 102評論 0 1
  • 一直沒弄清楚所謂的仲夏具體的是個什么時間段,姑且認為就是夏天的時候吧冯凹。陽歷六月的早上谎亩,早飯已經(jīng)進肚,窗外是鳴蟬一片...
    彎彎ww閱讀 172評論 0 1
  • 我是一個普通人 沒有遠大的理想抱負 只想過普通平凡的生活 一屋 兩人 三餐 四季 今天下班跟同事討論起夢想 跟大家...
    喜孜孜孜孜閱讀 353評論 0 1