JDK容器學習之LinkedHashMap (一):底層存儲結構分析

LinkedHashMap 底層存儲結構分析

HashMap 是無序的kv鍵值對容器,TreeMap 則是根據key進行排序的kv鍵值對容器,而LinkedHashMap一樣也是一個有序的kv鍵值對容器,區別是其排序方式依據的是進入Map的前後順序java

LinkedHashMap 繼承自 HashMap, 直接看其內部方法,並無覆蓋HashMap的增刪查詢接口,連tables數組也沒有從新覆蓋,因此數據結構基本沒啥變化node

1.繼承體系

首先看繼承體系以下編程

輸入圖片說明

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

看到這裏,有個地方比較有意思,HashMap 既然已經實現了Map接口,爲何 LinkedHashMap 也要另外實現 Map 接口?數組

  • 首先不顯示添加這個實現是沒有問題的,從繼承體系來說集成了 HashMap類,必然是實現了Map接口的
  • 非功能角度出發,這樣有什麼好處?
    • 繼承體系一目瞭然,從源碼上就能夠看出這個是Map接口的一個實現
    • 從源碼中很清楚就能夠得知Map中的方法,LinkedHashMap 能夠直接使用,若是沒有顯示加上,則須要向上查找父類中的提供的方法

從我的角度觸發,這應該是一種編程習慣的問題數據結構

2. 數據結構

一樣從put(k,v)方法出發,經過查看新增一個kv對,數據是如何保存的來肯定數據存儲結構,由於 LinkedHashMap 並無覆蓋 put() 方法,因此能夠肯定底層的存儲結構一致,那麼有序是如何保證的呢?學習

查看源碼,發現新增了兩個成員.net

/**
 * The head (eldest) of the doubly linked list.
 */
transient LinkedHashMap.Entry<K,V> head;

/**
 * The tail (youngest) of the doubly linked list.
 */
transient LinkedHashMap.Entry<K,V> tail;


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

上面兩個維護的是一個雙向鏈表的頭尾,這個鏈表根據插入Map的順序來維護Node節點的,以此保證了順序code

3. 雙向鏈表的維護

既然LinkedHashMapHashMap的基礎上維護了一個雙向鏈表,那麼這個鏈表的增刪修改的邏輯是怎樣的?對象

LinkedHashMap 擴展了 Entry類,新增了before, after, 分別指向該節點在鏈表中的先後節點blog

新建立一個節點

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

依然以put(k,v)做爲研究對象,分析鏈表的關係維護

主要方法: java.util.HashMap#putVal

1. 新增一個Map中已經存在的kv對

當插入已經存在的kv對時,不會建立新的Node節點,而會調用下面的方法

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

accessOrder true表示鏈表的順序根據訪問順序來,false表示根據插入順序來

默認會設置爲false,此時上面的邏輯基本上不走到,即表示插入一個已存在的kv對,不會修改鏈表的順序

若是顯示設置 accessOrder爲true,則會將修改的節點,放在鏈表的最後

2. 新增一個Map中不存在,且沒有hash碰撞

新增一個不存在的kv對,首先是調用上面的方法,建立一個Node節點: LinkedHashMap.Entry<K,V>, 在建立節點的同時,就已經將節點放在了鏈表的最後, 實現邏輯以下

// link at the end of list
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;
    }
}

3. 新增一個Map中不存在,但出現hash碰撞

同上

小結

  • LinkedHashMap 存儲結構和 HashMap 相同,依然是數組+鏈表+紅黑樹
  • LinkedHashMap 額外持有一個雙向鏈表,維護插入節點的順序
  • 最終的數據結構以下圖
    • 實際的元素存儲與HashMap一致,依然是數組+鏈表+紅黑樹的形式
    • 區別在於:
      • 除了維護數組+鏈表的結構以外,還根據插入Map前後順序維護了一個雙向鏈表的頭尾head,tail
      • Node基本結構,相比較HashMap而言,還增長了 before,after 兩個分別指向雙向鏈表中先後節點的屬性
      • 即下圖中的雙向鏈表中的節點,其實值依然是下面的數組+鏈表結構中的元素

相關博文

關注更多

掃一掃二維碼,關注小灰灰blog

小灰灰blog

相關文章
相關標籤/搜索