Java集合源碼分析之Map(六):LinkedHashMap

LinkedHashMapHashMap的子類,所以也具備HashMap的諸多特性痹升。不同的是建炫,LinkedHashMap還維護(hù)了一個雙向鏈表,以保證通過Iterator遍歷時順序與插入順序一致疼蛾。除此之外肛跌,它還支持Access Order,即按照元素被訪問的順序來排序察郁,我們熟知的LRUCache底層就依賴于此衍慎。以下是文檔中需要我們注意的點:

Hash table and linked list implementation of the Map interface, with predictable iteration order. This implementation differs from HashMap in that it maintains a doubly-linked list running through all of its entries. This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order). Note that insertion order is not affected if a key is re-inserted into the map.

A special LinkedHashMap(int,float,boolean) constructor is provided to create a linked hash map whose order of iteration is the order in which its entries were last accessed, from least-recently accessed to most-recently (access-order). This kind of map is well-suited to building LRU caches.

The removeEldestEntry(Map.Entry) method may be overridden to impose a policy for removing stale mappings automatically when new mappings are added to the map.

Note, however, that the penalty for choosing an excessively high value for initial capacity is less severe for this class than for HashMap, as iteration times for this class are unaffected by capacity.

下面我們就從構(gòu)造函數(shù)和成員變量開始分析其具體實現(xiàn)。

構(gòu)造函數(shù)與成員變量

成員變量

在分析成員變量前皮钠,我們先看下其存儲元素的結(jié)構(gòu)稳捆。

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);
    }
}

這個EntryHashMap中被引用過,主要是為了能讓LinkedHashMap也支持樹化麦轰。在這里則是用來存儲元素乔夯。

// 雙向鏈表的頭,用作AccessOrder時也是最老的元素
transient LinkedHashMap.Entry<K,V> head;

// 雙向鏈表的尾款侵,用作AccessOrder時也是最新的元素
transient LinkedHashMap.Entry<K,V> tail;

// true則為訪問順序末荐,false則為插入順序
final boolean accessOrder;

構(gòu)造函數(shù)

關(guān)于LinkedHashMap的構(gòu)造函數(shù)我們只關(guān)注一個,其他的都和HashMap類似新锈,只是把accessOrder設(shè)置為了false甲脏。在上邊的文檔說過,initialCapacity并沒有在HashMap中那般重要妹笆,因為鏈表不需要像數(shù)組那樣必須先聲明足夠的空間块请。下面這個構(gòu)造函數(shù)是支持訪問順序的。

public LinkedHashMap(int initialCapacity,
                    float loadFactor,
                    boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

重要方法

LinkedHashMap并沒有再實現(xiàn)一整套增刪改查的方法晾浴,而是通過復(fù)寫HashMap在此過程中定義的幾個方法來實現(xiàn)的负乡。對此不熟悉的可以查看文末關(guān)于HashMap分析的文章,或者對照HashMap的源碼來看脊凰。

插入一個元素

HashMap在插入時,調(diào)用了newNode來新建一個節(jié)點茂腥,或者是通過replacementNode來替換值狸涌。在樹化時也有兩個對應(yīng)的方法,分別是newTreeNodereplacementTreeNode最岗。完成之后帕胆,還調(diào)用了afterNodeInsertion方法,這個方法允許我們在插入完成后做些事情般渡,默認(rèn)是空實現(xiàn)懒豹。

為了方便分析芙盘,我們會對比HashMap中的實現(xiàn)與LinkedHashMap的實現(xiàn),來摸清它是如何做的脸秽。

// HashMap中的實現(xiàn)
Node<K, V> newNode(int hash, K key, V value, Node<K, V> next) {
    return new Node<>(hash, key, value, next);
}

// LinkedHashMap中的實現(xiàn)
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;
}

// HashMap中的實現(xiàn)
Node<K, V> replacementNode(Node<K, V> p, Node<K, V> next) {
    return new Node<>(p.hash, p.key, p.value, next);
}

// LinkedHashMap中的實現(xiàn)
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
    LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
    LinkedHashMap.Entry<K,V> t =
        new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);
    transferLinks(q, t);
    return t;
}

// newTreeNode和replacementTreeNode和此類似

通過以上對比儒老,可以發(fā)現(xiàn),LinkedHashMap在新增時记餐,調(diào)用了linkNodeLast驮樊,再替換時調(diào)用了transferLinks。以下是這兩個方法的實現(xiàn)片酝。

// 就是將元素掛在鏈尾
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;
    }
}

// 用dst替換src
private void transferLinks(LinkedHashMap.Entry<K,V> src,
                            LinkedHashMap.Entry<K,V> dst) {  
    LinkedHashMap.Entry<K,V> b = dst.before = src.before;
    LinkedHashMap.Entry<K,V> a = dst.after = src.after;
    if (b == null)
        head = dst;
    else
        b.after = dst;
    if (a == null)
        tail = dst;
    else
        a.before = dst;
}

最后我們看下afterNodeInsertion做了哪些事情吧:

// evict在HashMap中說過囚衔,為false表示是創(chuàng)建階段
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    // 不是創(chuàng)建階段
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        // 自動刪除最老的元素,也就是head元素
        removeNode(hash(key), key, null, false, true);
    }
}

removeEldestEntry是當(dāng)想要在插入元素時自動刪除最老的元素時需要復(fù)寫的方法雕沿。其默認(rèn)實現(xiàn)如下:

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

查詢

因為要支持訪問順序练湿,所以獲取元素的方法和HashMap也有所不同。下面我們看下其實現(xiàn):

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        // 數(shù)據(jù)被訪問审轮,需要將其移動到末尾
        afterNodeAccess(e);
    return e.value;
}

getNode方法是在HashMap中實現(xiàn)的鞠鲜,所以這是包裝了一下HashMap的方法,并添加了一個afterNodeAccess断国,其實現(xiàn)如下:

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    // e元素不在末尾
    if (accessOrder && (last = tail) != e) {
        // p是e贤姆,b是前一個元素,a是后一個元素
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        // e要放在末尾稳衬,所以沒有after
        p.after = null;

        // 把e去掉霞捡,把b和a接起來
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;

        //把e接在末尾
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

關(guān)于LinkedHashMap的分析就到這里了,其他關(guān)于Iterator的內(nèi)容都和Collection是大同小異的薄疚,感興趣的可以去查看相關(guān)源碼碧信。

上一篇:Java集合源碼分析之Map(五):HashMap

下一篇:Java集合源碼分析之Set概述


我是飛機醬,如果您喜歡我的文章街夭,可以關(guān)注我~

編程之路砰碴,道阻且長。唯板丽,路漫漫其修遠(yuǎn)兮呈枉,吾將上下而求索。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末埃碱,一起剝皮案震驚了整個濱河市猖辫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌砚殿,老刑警劉巖啃憎,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異似炎,居然都是意外死亡辛萍,警方通過查閱死者的電腦和手機悯姊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贩毕,“玉大人悯许,你說我怎么就攤上這事《保” “怎么了岸晦?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長睛藻。 經(jīng)常有香客問我启上,道長,這世上最難降的妖魔是什么店印? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任冈在,我火速辦了婚禮,結(jié)果婚禮上按摘,老公的妹妹穿的比我還像新娘包券。我一直安慰自己,他們只是感情好炫贤,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布溅固。 她就那樣靜靜地躺著,像睡著了一般兰珍。 火紅的嫁衣襯著肌膚如雪侍郭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天掠河,我揣著相機與錄音亮元,去河邊找鬼。 笑死唠摹,一個胖子當(dāng)著我的面吹牛爆捞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播勾拉,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼煮甥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了望艺?” 一聲冷哼從身側(cè)響起苛秕,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎找默,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吼驶,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡惩激,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年店煞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片风钻。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡顷蟀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出骡技,到底是詐尸還是另有隱情鸣个,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布布朦,位于F島的核電站囤萤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏是趴。R本人自食惡果不足惜涛舍,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唆途。 院中可真熱鬧富雅,春花似錦、人聲如沸肛搬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽温赔。三九已至蛤奢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間让腹,已是汗流浹背远剩。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留骇窍,地道東北人瓜晤。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像腹纳,于是被迫代替她去往敵國和親痢掠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

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

  • 今年年初的時候我就給自己設(shè)定了看書的目標(biāo)嘲恍,于是跟著星姐團(tuán)購了一些足画。又遇見了土豆姐,跟著土豆姐買了一些工具類的書佃牛。同...
    飛天蘿莉想閱讀 276評論 2 2
  • 下關(guān)風(fēng)俘侠,上關(guān)花象缀,蒼山雪蔬将,洱海月。 在大理還沒來得及好好感受央星,就匆匆離開了霞怀。 記得那晚從昆明做硬座到大理,晚上很累但...
    寂寞里的微笑閱讀 608評論 5 6
  • 今天去提車莉给,明白了一個道理毙石,并不是你身邊的人都會像你以為的那樣替你高興。即使是你的至親或者好友颓遏。每個人都是...
    鴨轉(zhuǎn)非閱讀 243評論 0 0