Java集合源碼分析-LinkedHashMap

LinkedHashMap繼承自HashMap,HashMap是無(wú)序的鲸拥,而LinkedHashMap則利用雙向非循環(huán)結(jié)構(gòu)保持了插入節(jié)點(diǎn)的順序(有兩種順序拐格,插入順序和訪問(wèn)順序,后面解釋)刑赶。

建議先搞明白HashMap的原理再看LinkedHashMap捏浊,Java集合源碼分析-HashMap

LinkedHashMap類圖


和HashMap不同的地方在于它通過(guò)改造newNode方法和添加下面三個(gè)方法:

void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

LinkedHashMap成員變量和構(gòu)造器

LinkedHashMap繼承了HashMap的所有的非private的變量撞叨,而且還多出來(lái)三個(gè)變量:

    transient LinkedHashMap.Entry<K,V> head;
    transient LinkedHashMap.Entry<K,V> tail;
    final boolean accessOrder;

head和tail很簡(jiǎn)單金踪,分別是頭結(jié)點(diǎn)和尾節(jié)點(diǎn),不解釋牵敷,但是accessOrder就需要解釋下了胡岔。accessOrder,看它的字面意思是訪問(wèn)順序枷餐,那么就跟LinkedHashMap的節(jié)點(diǎn)順序有關(guān)了靶瘸,默認(rèn)accessOrder是false,表示LinkedHashMap的節(jié)點(diǎn)順序和插入順序是一樣的毛肋,如果accessOrder為true怨咪,那么在get方法中會(huì)調(diào)用afterNodeAccess方法,看下這個(gè)方法的源碼:

    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

這個(gè)方法做的操作是將get訪問(wèn)的節(jié)點(diǎn)掐出來(lái)并挪到尾節(jié)點(diǎn)润匙,這就是所謂的訪問(wèn)順序诗眨,想想跟插入順序的區(qū)別。java.util包下的集合的迭代器遍歷都是fail-fast的趁桃,遍歷時(shí)候更改集合的結(jié)構(gòu)會(huì)拋出ConcurrentModificationException辽话,一般來(lái)說(shuō)get查詢不會(huì)更改集合的結(jié)構(gòu)肄鸽,但是accessOrder為true的LinkedHashMap的get查詢會(huì)更改集合的結(jié)構(gòu)卫病,可以看到上面代碼的最后一行對(duì)modCount進(jìn)行了加一操作油啤。所以,如果LinkedHashMap的accessOrder為true蟀苛,那么在迭代器遍歷的時(shí)候千萬(wàn)別使用get方法益咬。
舉個(gè)例子:

        Map<String, String> linkedHashMap = new LinkedHashMap<>(16, 0.75f, true);
        linkedHashMap.put("1", "callme1");
        linkedHashMap.put("2", "callme2");
        linkedHashMap.put("3", "callme3");
        linkedHashMap.put("4", "callme4");
        System.out.println("默認(rèn)插入順序:");
        Set<Map.Entry<String, String>> set = linkedHashMap.entrySet();
        Iterator<Map.Entry<String, String>> iterator = set.iterator();
        while(iterator.hasNext()) {
            Map.Entry entry = iterator.next();
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            System.out.println("key:" + key + ",value:" + value);
        }
        System.out.println("通過(guò)get方法,導(dǎo)致key為1對(duì)應(yīng)的Entry挪到尾部");
        linkedHashMap.get("2");
        Set<Map.Entry<String, String>> set2 = linkedHashMap.entrySet();
        Iterator<Map.Entry<String, String>> iterator2 = set2.iterator();
        while(iterator2.hasNext()) {
            Map.Entry entry = iterator2.next();
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            System.out.println("key:" + key + ",value:" + value);
        }

LinkedHashMap比HashMap的構(gòu)造器多了一個(gè):

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

可以看出這個(gè)構(gòu)造器只是來(lái)處理accessOrder 而已帜平。

LinkedHashMap節(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);
        }
    }

可以看到LinkedHashMap節(jié)點(diǎn)類繼承自HashMap節(jié)點(diǎn)類幽告,來(lái)復(fù)習(xí)下HashMap的節(jié)點(diǎn)類:

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
    }

LinkedHashMap節(jié)點(diǎn)類多了before和after兩個(gè)屬性,注意after和next兩者區(qū)別裆甩,不需要解釋了冗锁,通過(guò)before和after保持LinkedHashMap的有序性。

put

為了保持有序性嗤栓,LinkedHashMap的put操作和HashMap的put有三點(diǎn)不同:
第一點(diǎn)不同是生成新節(jié)點(diǎn)的方法newNode不同(調(diào)用這個(gè)方法表示表中不存在重復(fù)key節(jié)點(diǎn))冻河,LinkedHashMap多調(diào)用了一個(gè)方法linkNodeLast:

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

很簡(jiǎn)單,只是將新節(jié)點(diǎn)插入到順序表的尾部茉帅。
第二點(diǎn)不同是叨叙,如果表中存在和要插入節(jié)點(diǎn)的key相同的節(jié)點(diǎn),覆蓋之后會(huì)調(diào)用afterNodeAccess方法堪澎,前面介紹過(guò)擂错,很簡(jiǎn)單。
第三點(diǎn)不同是LinkedHashMap在插入的最后一步調(diào)用了afterNodeInsertion(boolean evict)方法:

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

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

從代碼里可以看出樱蛤,afterNodeInsertion試圖將最老的節(jié)點(diǎn)即頭結(jié)點(diǎn)刪除钮呀,但是removeEldestEntry永遠(yuǎn)返回false,所以afterNodeInsertion什么都不會(huì)干的昨凡。afterNodeInsertion有什么卵用嗎爽醋?這個(gè)removeEldestEntry方法應(yīng)該需要用戶重寫的,下面的測(cè)試代碼:

        final int MAX_ENTRIES = 8;
        LinkedHashMap lhm = new LinkedHashMap(MAX_ENTRIES, 0.75F, false) {
            protected boolean removeEldestEntry(Map.Entry eldest) {
                if (size() > MAX_ENTRIES) {
                    if (isImportant(eldest)) {
                        //Handle an important entry here, like reinserting it to the back of the list
                        this.remove(eldest.getKey());
                        this.put(eldest.getKey(), eldest.getValue());
                        //removeEldestEntry will be called again, now with the next entry
                        //so the size should not exceed the MAX_ENTRIES value
                        //WARNING: If every element is important, this will loop indefinetly!
                    } else {
                        return true; //Element is unimportant
                    }
                    return false; //Size not reached or eldest element was already handled otherwise
                }
                return false; //Size not reached or eldest element was already handled otherwise
            }
        };
        lhm.put(1, "1");
        lhm.put(2, "2");
        lhm.put(3, "3");
        lhm.put(4, "4");
        lhm.put(5, "5");
        lhm.put(6, "6");
        lhm.put(7, "7");
        lhm.put(8, "8");
        lhm.put(9, "9");
        lhm.put(10, "10");
        System.out.println("" + lhm);

結(jié)果輸出如圖所示:

可以看到map中最多只有8個(gè)節(jié)點(diǎn)土匀,超出的話會(huì)把老的刪除子房。

remove

LinkedHashMap的刪除比HashMap的刪除多了一步afterNodeRemoval:

    void afterNodeRemoval(Node<K,V> e) { // unlink
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.before = p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a == null)
            tail = b;
        else
            a.before = b;
    }

主要處理before和after,很簡(jiǎn)單就轧。

遍歷

HashMap遍歷的核心類是HashIterator证杭,而LinkedHashMap遍歷的核心類是LinkedHashIterator,很簡(jiǎn)單妒御,核心方法是nextNode:

        final LinkedHashMap.Entry<K,V> nextNode() {
            LinkedHashMap.Entry<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            current = e;
            next = e.after;
            return e;
        }

保持有序遍歷最關(guān)鍵的一行代碼是第八行:next = e.after;解愤。

最后附一張LinkedHashMap的底層結(jié)構(gòu)圖,雙向非循環(huán)鏈表:


雙向非循環(huán)鏈表
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末乎莉,一起剝皮案震驚了整個(gè)濱河市送讲,隨后出現(xiàn)的幾起案子奸笤,更是在濱河造成了極大的恐慌,老刑警劉巖哼鬓,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件监右,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡异希,警方通過(guò)查閱死者的電腦和手機(jī)健盒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)称簿,“玉大人扣癣,你說(shuō)我怎么就攤上這事『┙担” “怎么了父虑?”我有些...
    開(kāi)封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)授药。 經(jīng)常有香客問(wèn)我士嚎,道長(zhǎng),這世上最難降的妖魔是什么烁焙? 我笑而不...
    開(kāi)封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任航邢,我火速辦了婚禮,結(jié)果婚禮上骄蝇,老公的妹妹穿的比我還像新娘膳殷。我一直安慰自己,他們只是感情好九火,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布赚窃。 她就那樣靜靜地躺著,像睡著了一般岔激。 火紅的嫁衣襯著肌膚如雪勒极。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天虑鼎,我揣著相機(jī)與錄音辱匿,去河邊找鬼。 笑死炫彩,一個(gè)胖子當(dāng)著我的面吹牛匾七,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播江兢,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼昨忆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了杉允?” 一聲冷哼從身側(cè)響起邑贴,我...
    開(kāi)封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤席里,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后拢驾,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體奖磁,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年独旷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了署穗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寥裂。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嵌洼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出封恰,到底是詐尸還是另有隱情麻养,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布诺舔,位于F島的核電站鳖昌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏低飒。R本人自食惡果不足惜许昨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望褥赊。 院中可真熱鬧糕档,春花似錦、人聲如沸拌喉。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)尿背。三九已至端仰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間田藐,已是汗流浹背荔烧。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留汽久,地道東北人鹤竭。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像回窘,于是被迫代替她去往敵國(guó)和親诺擅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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