JDK1.8源碼閱讀--LinkedHashMap

一庸追、LinkedHashMap的屬性

二、LinkedHashMap的構(gòu)造方法

三涝缝、LinkedHashMap的重要函數(shù)

1. afterNodeAccess函數(shù)

2. afterNodeInsertion函數(shù)

3. afterNodeRemoval函數(shù)

4. transferLinks函數(shù)

5. linkNodeLast函數(shù)

6. 其他方法

四扑庞、LinkedHashMap的迭代器

1. 基礎(chǔ)迭代器--LinkedHashIterator

2. 鍵迭代器--LinkedKeyIterator

3. 值迭代器--LinkedValueIterator

4. 結(jié)點(diǎn)迭代器--LinkedEntryIterator

LinkedHashMap<K,V>繼承HashMap<K,V>類,實(shí)現(xiàn)了 Map<K,V>接口拒逮。

LinkedHashMap是HashMap的子類罐氨,與HashMap有著同樣的存儲(chǔ)結(jié)構(gòu),但它加入了一個(gè)雙向鏈表的頭結(jié)點(diǎn)滩援,將所有put到LinkedHashmap的節(jié)點(diǎn)一一串成了一個(gè)雙向循環(huán)鏈表栅隐,因此它保留了節(jié)點(diǎn)插入的順序,可以使節(jié)點(diǎn)的輸出順序與輸入順序相同玩徊。

LinkedHashMap可以用來(lái)實(shí)現(xiàn)LRU算法(這會(huì)在下面的源碼中進(jìn)行分析)租悄。

LinkedHashMap同樣是非線程安全的,只在單線程環(huán)境下使用恩袱。  

1. LinkedHashMap的屬性

    /**
     * 靜態(tài)內(nèi)部類Entry表示雙向鏈表泣棋,繼承自HashMap的Node,在其基礎(chǔ)上加上了before和after兩個(gè)指針
     * 雙向循環(huán)鏈表的頭結(jié)點(diǎn)畔塔,整個(gè)LinkedHashMap中只有一個(gè)header
     * 它將哈希表中所有的Entry貫穿起來(lái)潭辈,header中不保存key-value對(duì)纪吮,只保存前后節(jié)點(diǎn)的引用  
     */
    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);
        }
    }
  • private static final long serialVersionUID = 3801124242820219131L;
    序列化
  • transient LinkedHashMap.Entry<K,V> head;
    鏈表頭結(jié)點(diǎn)
  • transient LinkedHashMap.Entry<K,V> tail;
    鏈表尾結(jié)點(diǎn)
  • final boolean accessOrder;
    雙向鏈表中元素排序規(guī)則的標(biāo)志位;
    false:表示按插入順序排序萎胰;true:表示按訪問(wèn)順序排序

2. LinkedHashMap的構(gòu)造方法

2.1 LinkedHashMap(int, float)型構(gòu)造函數(shù)

    /**
     * 總是會(huì)在構(gòu)造函數(shù)的第一行調(diào)用父類構(gòu)造函數(shù)碾盟,使用super關(guān)鍵字,    
     * accessOrder默認(rèn)為false技竟,access為true表示之后訪問(wèn)順序按照元素的訪問(wèn)順序進(jìn)行
     * 即不按照之前的插入順序了,access為false表示按照插入順序訪問(wèn)熙尉。
     */
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

2.2 LinkedHashMap(int)型構(gòu)造函數(shù)

    /**
     * 加載因子取默認(rèn)的0.75f
     */    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

2.3 LinkedHashMap()型構(gòu)造函數(shù)

    /**
     * 加載因子取默認(rèn)的0.75f检痰,容量取默認(rèn)的16 
     */
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

2.4 LinkedHashMap(Map<? extends K, ? extends V>)型構(gòu)造函數(shù)

    /**
     * putMapEntries是調(diào)用到父類HashMap的構(gòu)造函數(shù)
     */
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

2.5 LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)型構(gòu)造函數(shù)

    /**
     * 可以指定accessOrder的值,從而控制訪問(wèn)順序
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

3. LinkedHashMap的重要函數(shù)

  • afterNodeAccess椎椰、afterNodeInsertion慨飘、afterNodeRemoval這三個(gè)方法都是重寫父類HashMap的方法瓤的。

3.1 afterNodeAccess函數(shù)

    /**
     * 把當(dāng)前節(jié)點(diǎn)e放到雙向鏈表尾部
     */
    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        /* accessOrder就是我們前面說(shuō)的LRU控制圈膏,當(dāng)它為true本辐,同時(shí)e對(duì)象不是尾節(jié)點(diǎn)
        (如果訪問(wèn)尾節(jié)點(diǎn)就不需要設(shè)置医增,該方法就是把節(jié)點(diǎn)放置到尾節(jié)點(diǎn))
        */
        if (accessOrder && (last = tail) != e) {
            // 用a和b分別記錄該節(jié)點(diǎn)前面和后面的節(jié)點(diǎn)
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            // 釋放當(dāng)前節(jié)點(diǎn)與后節(jié)點(diǎn)的關(guān)系 
            p.after = null;
            // 如果當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn)是空
            if (b == null)
                // 那么頭節(jié)點(diǎn)就設(shè)置為a
                head = a;
            else
                // 如果b不為null叶骨,那么b的后節(jié)點(diǎn)指向a
                b.after = a;
            // 如果a節(jié)點(diǎn)不為空
            if (a != null)
                // a的后節(jié)點(diǎn)指向b
                a.before = b;
            else
                // 如果a為空忽刽,那么b就是尾節(jié)點(diǎn)
                last = b;
            // 如果尾節(jié)點(diǎn)為空
            if (last == null)
                // 那么p為頭節(jié)點(diǎn)
                head = p;
            else {
                // 否則就把p放到雙向鏈表最尾處
                p.before = last;
                last.after = p;
            }
            // 設(shè)置尾節(jié)點(diǎn)為P  
            tail = p;
            // LinkedHashMap對(duì)象操作次數(shù)+1
            ++modCount;
        }
    }

此函數(shù)在很多函數(shù)(如put)中都會(huì)被回調(diào)跪帝,LinkedHashMap重寫了HashMap中的此函數(shù)伞剑。若訪問(wèn)順序?yàn)閠rue黎泣,且訪問(wèn)的對(duì)象不是尾結(jié)點(diǎn)

訪問(wèn)結(jié)點(diǎn)3的前后狀態(tài)

從圖中可以看到抒倚,結(jié)點(diǎn)3鏈接到了尾結(jié)點(diǎn)后面
LinkedHashMap重寫了HashMap的get方法:

    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)    // 如果啟用了LRU規(guī)則
            afterNodeAccess(e);  // 那么把該節(jié)點(diǎn)移到雙向鏈表最后面
        return e.value;
    }
  • 注: LinkedHashMap壓根沒(méi)有重寫put方法含蓉,每次用LinkedHashMap的put方法的時(shí)候项郊,其實(shí)你調(diào)用的都是HashMap的put方法呆抑。但它也會(huì)執(zhí)行afterNodeAccess()方法鹊碍,因?yàn)檫@個(gè)方法HashMap就是存在的,但是沒(méi)有實(shí)現(xiàn)公罕,LinkedHashMap重寫了afterNodeAccess()這個(gè)方法楼眷。

3.2 afterNodeInsertion函數(shù)

    /**
     * 在哈希表中插入了一個(gè)新節(jié)點(diǎn)時(shí)調(diào)用的罐柳,它會(huì)把鏈表的頭節(jié)點(diǎn)刪除掉张吉,刪除的方式是通過(guò)調(diào)用HashMap的removeNode方法
     */
    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

3.3 afterNodeRemoval函數(shù)

    /**
     * 當(dāng)HashMap刪除一個(gè)鍵值對(duì)時(shí)調(diào)用的肮蛹,它會(huì)把在HashMap中刪除的那個(gè)鍵值對(duì)一并從鏈表中刪除伦忠,保證了哈希表和鏈表的一致性。
     */
    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

3.4 transferLinks函數(shù)

    /**
     * 用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;
    }
使用結(jié)點(diǎn)7替換結(jié)點(diǎn)3

3.5 linkNodeLast函數(shù)

在看linkNodeLast函數(shù)之前先看看newNode和newTreeNode這兩個(gè)函數(shù)

3.5.1 newNode函數(shù)

    /**
     * 當(dāng)桶中結(jié)點(diǎn)類型為HashMap.Node類型時(shí)未桥,調(diào)用此函數(shù)
     */
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        // 生成Node結(jié)點(diǎn)    
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        // 將該結(jié)點(diǎn)插入雙鏈表末尾
        linkNodeLast(p);
        return p;
    }

3.5.2 newTreeNode函數(shù)

    /**
     * 當(dāng)桶中結(jié)點(diǎn)類型為HashMap.TreeNode時(shí)冬耿,調(diào)用此函數(shù)
     */
    TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
        // 生成TreeNode結(jié)點(diǎn)
        TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
        // 將該結(jié)點(diǎn)插入雙鏈表末尾
        linkNodeLast(p);
        return p;
    }

3.5.3 linkNodeLast函數(shù)

    /**
     * 把新加的節(jié)點(diǎn)放在鏈表的最后面
     */
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        // 將tail給臨時(shí)變量last
        LinkedHashMap.Entry<K,V> last = tail;
        // 把new的Entry給tail
        tail = p;
        // 若沒(méi)有l(wèi)ast亦镶,說(shuō)明p是第一個(gè)節(jié)點(diǎn)爱咬,head=p
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }

3.6 其他方法

  • internalWriteEntries方法
    /**
     * 該方法也是重寫父類HashMap的方法绊起,也是為了LinkedHashMap鍵和值被序列化的存儲(chǔ)順序
     */
    void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
            s.writeObject(e.key);
            s.writeObject(e.value);
        }
    }

其他方法就不一一介紹的官方API中都有詳細(xì)的說(shuō)明虱歪。

4. LinkedHashMap的迭代器

4.1基礎(chǔ)迭代器--LinkedHashIterator

為抽象類,用于對(duì)LinkedHashMap進(jìn)行迭代

    /**
     * LinkedHashIterator是LinkedHashMap的迭代器师枣,為抽象類践美,用于對(duì)LinkedHashMap進(jìn)行迭代
     */
    abstract class LinkedHashIterator {
        // 下一個(gè)結(jié)點(diǎn)
        LinkedHashMap.Entry<K,V> next;
        // 當(dāng)前結(jié)點(diǎn)
        LinkedHashMap.Entry<K,V> current;
        // 期望的修改次數(shù)
        int expectedModCount;

        LinkedHashIterator() {
            // next賦值為頭結(jié)點(diǎn)
            next = head;
            // 賦值修改次數(shù)
            expectedModCount = modCount;
            // 當(dāng)前結(jié)點(diǎn)賦值為空
            current = null;
        }
        // 是否存在下一個(gè)結(jié)點(diǎn)
        public final boolean hasNext() {
            return next != null;
        }

        final LinkedHashMap.Entry<K,V> nextNode() {
            LinkedHashMap.Entry<K,V> e = next;
            // 檢查是否存在結(jié)構(gòu)性修改
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            // 當(dāng)前結(jié)點(diǎn)是否為空
            if (e == null)
                throw new NoSuchElementException();
            // 賦值當(dāng)前節(jié)點(diǎn)
            current = e;
            // 賦值下一個(gè)結(jié)點(diǎn)
            next = e.after;
            return e;
        }

        public final void remove() {
            // 保存當(dāng)前結(jié)點(diǎn)
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            // 移除結(jié)點(diǎn)
            removeNode(hash(key), key, null, false, false);
            // 更新最新修改數(shù)
            expectedModCount = modCount;
        }
    }

4.2 鍵迭代器--LinkedKeyIterator

繼承自LinkedHashIterator,實(shí)現(xiàn)了Iterator接口玫膀,對(duì)LinkedHashMap中的鍵進(jìn)行迭代

    final class LinkedKeyIterator extends LinkedHashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().getKey(); }
    }

4.3 值迭代器--LinkedValueIterator

繼承自LinkedHashIterator帖旨,實(shí)現(xiàn)了Iterator接口灵妨,對(duì)LinkedHashMap中的值進(jìn)行迭代

    final class LinkedValueIterator extends LinkedHashIterator
        implements Iterator<V> {
        public final V next() { return nextNode().value; }
    }

4.4 結(jié)點(diǎn)迭代器--LinkedEntryIterator

繼承自LinkedHashIterator货抄,實(shí)現(xiàn)了Iterator接口朱转,對(duì)LinkedHashMap中的結(jié)點(diǎn)進(jìn)行迭代

    final class LinkedEntryIterator extends LinkedHashIterator
        implements Iterator<Map.Entry<K,V>> {
        public final Map.Entry<K,V> next() { return nextNode(); }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末怪与,一起剝皮案震驚了整個(gè)濱河市分别,隨后出現(xiàn)的幾起案子耘斩,更是在濱河造成了極大的恐慌括授,老刑警劉巖刽脖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件曲管,死亡現(xiàn)場(chǎng)離奇詭異院水,居然都是意外死亡檬某,警方通過(guò)查閱死者的電腦和手機(jī)恢恼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)胰默,“玉大人场斑,你說(shuō)我怎么就攤上這事漓踢。” “怎么了漏隐?”我有些...
    開(kāi)封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵喧半,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我青责,道長(zhǎng)挺据,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任脖隶,我火速辦了婚禮扁耐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘做葵。我一直安慰自己怎燥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悬襟。 梳的紋絲不亂的頭發(fā)上割捅,一...
    開(kāi)封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天颊乘,我揣著相機(jī)與錄音檩小,去河邊找鬼卵惦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的姥敛。 我是一名探鬼主播金顿,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼播揪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了疆前?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤爆惧,失蹤者是張志新(化名)和其女友劉穎叔收,沒(méi)想到半個(gè)月后复濒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體乒省,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盏筐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腹忽。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡着裹,死狀恐怖米同,靈堂內(nèi)的尸體忽然破棺而出少孝,到底是詐尸還是另有隱情,我是刑警寧澤年柠,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布傲武,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏昭齐。R本人自食惡果不足惜尿招,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望阱驾。 院中可真熱鬧就谜,春花似錦、人聲如沸里覆。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)喧枷。三九已至虹统,卻和暖如春弓坞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背车荔。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工渡冻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人忧便。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓族吻,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親珠增。 傳聞我的和親對(duì)象是個(gè)殘疾皇子超歌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345