Java集合(九) —— LinkedHashMap源碼分析

Java集合(一) —— Collection源碼分析
Java集合(二) —— ArrayList源碼分析
Java集合(三) —— LinkedList源碼分析
Java集合(四) —— PriorityQueue源碼分析
Java集合(五) —— HashSet源碼分析
Java集合(六) —— LinkedHashSet源碼分析
Java集合(七) —— TreeSet源碼分析
Java集合(八) —— HashMap源碼分析
Java集合(九) —— LinkedHashMap源碼分析
Java集合(十) —— TreeMap源碼分析

1.總結(jié)

1.LinkedHashMap繼承自HashMap,所以HashMap有的特性LinkedHashMap都有,比如數(shù)據(jù)結(jié)構(gòu)為數(shù)組+鏈表+紅黑樹荷愕,默認(rèn)容量為16辅辩,負(fù)載因子為0.75等(HashMap源碼分析)。
2.LinkedHashMap使用雙向鏈表維持?jǐn)?shù)據(jù)的插入順序或訪問順序(默認(rèn)是以插入順序排序)夭问。
3.當(dāng)以訪問順序排序時(shí),被訪問的節(jié)點(diǎn)都要從當(dāng)前位置移到鏈表尾部。

2.繼承關(guān)系圖

LinkedHashMap.png

3.源碼分析

3.1成員變量分析

// 雙向鏈表的頭結(jié)點(diǎn)
transient LinkedHashMap.Entry<K,V> head;

// 雙向鏈表的尾結(jié)點(diǎn)
transient LinkedHashMap.Entry<K,V> tail;

// 排序方式旅敷,false:插入順序;true:訪問順序
final boolean accessOrder;

3.2構(gòu)造方法分析

/**
 * 指定初始化容量和負(fù)載因子
 * accessOrder:默認(rèn)都為false颤霎,表示以插入順序排序
 */
public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}

/**
 * 指定初始化容量
 */
public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}

/**
 * 默認(rèn)構(gòu)造
 */
public LinkedHashMap() {
    super();
    accessOrder = false;
}

/**
 * 用已存在的map初始化LinkedHashMap
 */
public LinkedHashMap(Map<? extends K, ? extends V> m) {
    super();
    accessOrder = false;
    putMapEntries(m, false);
}

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder; // 指定是否以訪問順序排序
}

3.3常用方法分析

1.put方法

調(diào)用的就是HashMap的put方法媳谁,不同的是LinkedHashMap重寫了newNode方法,實(shí)現(xiàn)了afterNodeAccess和afterNodeInsertion方法

/**
 * 新建節(jié)點(diǎn)友酱,新節(jié)點(diǎ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);
    // 將節(jié)點(diǎn)接在雙向鏈表尾部
    linkNodeLast(p);
    return p;
}

/**
 * 將節(jié)點(diǎn)連接到鏈表尾部
 */
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    // 將尾結(jié)點(diǎn)指向p
    tail = p;
    // 如果last為null晴音,表示鏈表還沒有建立
    if (last == null)
        head = p;
    else {
        // 將新節(jié)點(diǎn)接到鏈表尾部,新節(jié)點(diǎn)的前向指針指向last缔杉,last的后繼指針指向新節(jié)點(diǎn)
        p.before = last;
        last.after = p;
    }
}

再看看實(shí)現(xiàn)的兩個(gè)方法:

  • afterNodeAccess方法
void afterNodeAccess(Node<K,V> e) { // move node to last 將節(jié)點(diǎn)移到鏈表最后
    LinkedHashMap.Entry<K,V> last;
    // 當(dāng)以訪問順序排序锤躁,且e不是尾結(jié)點(diǎn)時(shí)
    if (accessOrder && (last = tail) != e) {
        // p => e; b => p的前驅(qū); a => p的后繼
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        if (b == null) // 表示p原先為頭節(jié)點(diǎn)
            // 將p移到鏈表尾部后,p的后繼節(jié)點(diǎn)a成為頭結(jié)點(diǎn)
            head = a;
        else
            // 否則將b的后繼指向a(此時(shí)p從鏈表中刪除或详,將斷開的鏈表重新連接起來)
            b.after = a;
        if (a != null)
            // a不為null系羞,則a的前驅(qū)指向b
            a.before = b;
        else
            // 否則b成為最后一個(gè)節(jié)點(diǎn)
            last = b;
        if (last == null)
            // 鏈表只有p節(jié)點(diǎn)郭计,將頭結(jié)點(diǎn)指向p
            head = p;
        else {
            // 否則將p節(jié)點(diǎn)連接到鏈表尾部
            p.before = last;
            last.after = p;
        }
        // 尾結(jié)點(diǎn)指向p
        tail = p;
        ++modCount;
    }
}
  • afterNodeInsertion方法
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        // 刪除節(jié)點(diǎn)
        removeNode(hash(key), key, null, false, true);
    }
}

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

從源碼可以看到,需要同時(shí)滿足三個(gè)條件才會(huì)才會(huì)進(jìn)入if語句
1.evict為true
2.first不為null
3.removeEldestEntry(first)方法返回true
然而默認(rèn)的removeEldestEntry(first)始終返回false觉啊,也就是說默認(rèn)不會(huì)刪除節(jié)點(diǎn)拣宏。removeEldestEntry(first)用于定義刪除最老元素的規(guī)則。

2.get方法

public V get(Object key) {
    Node<K,V> e;
    // getNode()方法為查找節(jié)點(diǎn)的具體實(shí)現(xiàn)杠人,在HashMap中已經(jīng)分析過勋乾,這里不再說明
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // 如果accessOrder為true,表示以訪問順序排序
    if (accessOrder)
        // 調(diào)整鏈表嗡善,將訪問的節(jié)點(diǎn)移到鏈表尾部
        afterNodeAccess(e);
    return e.value;
}

3.remove方法

調(diào)用的就是HashMAp的remove方法辑莫,不同的是LinkedHashMap實(shí)現(xiàn)了afterNodeRemoval方法。

/**
 * e:待刪除節(jié)點(diǎn)
 */
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;
}

afterNodeRemoval()實(shí)現(xiàn)從雙向鏈表中刪除節(jié)點(diǎn)罩引,并將斷開的鏈表重新連接起來各吨。

4.entrySet,keySet袁铐,values方法

entrySet揭蜒,keySet,values與HashMap中的這三個(gè)方法大同小異剔桨,其中entrySet和keySet通過迭代器將鍵值對/鍵映射到entrySet和keySet上屉更,values是一個(gè)Collection集合:

/**
 * 跟HashMap的實(shí)現(xiàn)是一樣的
 */
public Collection<V> values() {
    Collection<V> vs = values;
    if (vs == null) {
        vs = new LinkedValues();
        values = vs;
    }
    return vs;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市洒缀,隨后出現(xiàn)的幾起案子瑰谜,更是在濱河造成了極大的恐慌,老刑警劉巖树绩,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萨脑,死亡現(xiàn)場離奇詭異,居然都是意外死亡饺饭,警方通過查閱死者的電腦和手機(jī)渤早,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瘫俊,“玉大人蛛芥,你說我怎么就攤上這事【” “怎么了仅淑?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胸哥。 經(jīng)常有香客問我涯竟,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任庐船,我火速辦了婚禮银酬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘筐钟。我一直安慰自己揩瞪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布篓冲。 她就那樣靜靜地躺著李破,像睡著了一般。 火紅的嫁衣襯著肌膚如雪壹将。 梳的紋絲不亂的頭發(fā)上嗤攻,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音诽俯,去河邊找鬼妇菱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛暴区,可吹牛的內(nèi)容都是我干的闯团。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼仙粱,長吁一口氣:“原來是場噩夢啊……” “哼偷俭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起缰盏,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎淹遵,沒想到半個(gè)月后口猜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡透揣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年济炎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辐真。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡须尚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出侍咱,到底是詐尸還是另有隱情耐床,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布楔脯,位于F島的核電站撩轰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜堪嫂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一偎箫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧皆串,春花似錦淹办、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至寂玲,卻和暖如春塔插,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拓哟。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工想许, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人断序。 一個(gè)月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓流纹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親违诗。 傳聞我的和親對象是個(gè)殘疾皇子漱凝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

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