1. 程式人生 > >面試必備:LinkedHashMap原始碼解析(JDK8)

面試必備:LinkedHashMap原始碼解析(JDK8)

概括的說,LinkedHashMap 是一個關聯陣列、雜湊表,它是執行緒不安全的,允許key為null,value為null。 
它繼承自HashMap,實現了Map<K,V>介面。其內部還維護了一個雙向連結串列,在每次插入資料,或者訪問、修改資料時,會增加節點、或調整連結串列的節點順序。以決定迭代時輸出的順序。

預設情況,遍歷時的順序是按照插入節點的順序。這也是其與HashMap最大的區別。 
也可以在構造時傳入accessOrder引數,使得其遍歷順序按照訪問的順序輸出。

因繼承自HashMap,所以HashMap上文分析的特點,除了輸出無序,其他LinkedHashMap

都有,比如擴容的策略,雜湊桶長度一定是2的N次方等等。 
LinkedHashMap在實現時,就是重寫override了幾個方法。以滿足其輸出序列有序的需求。

示例程式碼:

根據這段例項程式碼,先從現象看一下LinkedHashMap的特徵: 
在每次插入資料,或者訪問、修改資料時,會增加節點、或調整連結串列的節點順序。以決定迭代時輸出的順序。

        Map<String, String> map = new LinkedHashMap<>();
        map.put("1", "a");
        map.put("2", "b"
); map.put("3", "c"); map.put("4", "d"); Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } System.out.println("以下是accessOrder=true的情況:"
); map = new LinkedHashMap<String, String>(10, 0.75f, true); map.put("1", "a"); map.put("2", "b"); map.put("3", "c"); map.put("4", "d"); map.get("2");//2移動到了內部的連結串列末尾 map.get("4");//4調整至末尾 map.put("3", "e");//3調整至末尾 map.put(null, null);//插入兩個新的節點 null map.put("5", null);//5 iterator = map.entrySet().iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

輸出:

1=a
2=b
3=c
4=d
以下是accessOrder=true的情況:
1=a
2=b
4=d
3=e
null=null
5=null
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3 節點

LinkedHashMap的節點Entry<K,V>繼承自HashMap.Node<K,V>,在其基礎上擴充套件了一下。改成了一個雙向連結串列

    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);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

同時類裡有兩個成員變數head tail,分別指向內部雙向連結串列的表頭、表尾。

    //雙向連結串列的頭結點
    transient LinkedHashMap.Entry<K,V> head;

    //雙向連結串列的尾節點
    transient LinkedHashMap.Entry<K,V> tail;
  • 1
  • 2
  • 3
  • 4
  • 5

4 建構函式

    //預設是false,則迭代時輸出的順序是插入節點的順序。若為true,則輸出的順序是按照訪問節點的順序。
    //為true時,可以在這基礎之上構建一個LruCach
    final boolean accessOrder;

    public LinkedHashMap() {
        super();
        accessOrder = false;
    }
    //指定初始化時的容量,
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }
    //指定初始化時的容量,和擴容的載入因子
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }
    //指定初始化時的容量,和擴容的載入因子,以及迭代輸出節點的順序
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }
    //利用另一個Map 來構建,
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        //該方法上文分析過,批量插入一個map中的所有資料到 本集合中。
        putMapEntries(m, false);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

小結: 
建構函式和HashMap相比,就是增加了一個accessOrder引數。用於控制迭代時的節點順序。

5 增

LinkedHashMap並沒有重寫任何put方法。但是其重寫了構建新節點的newNode()方法. 
newNode()會在HashMapputVal()方法裡被呼叫,putVal()方法會在批量插入資料putMapEntries(Map<? extends K, ? extends V> m, boolean evict)或者插入單個數據public V put(K key, V value)時被呼叫。

LinkedHashMap重寫了newNode(),在每次構建新節點時,通過linkNodeLast(p);新節點連結在內部雙向連結串列的尾部

    //在構建新節點時,構建的是`LinkedHashMap.Entry` 不再是`Node`.
    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;
    }
    //將新增的節點,連線在連結串列的尾部
    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;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

以及HashMap專門預留給LinkedHashMapafterNodeAccess() afterNodeInsertion() afterNodeRemoval() 方法。

    // Callbacks to allow LinkedHashMap post-actions
    void afterNodeAccess(Node<K,V> p) { }//訪問元素之後呼叫,accessOrder為true的情況下,將訪問的元素移到連結串列末尾,更新雙向連結串列
    void afterNodeInsertion(boolean evict) { }//新增元素之後呼叫,根據evict判斷是否需要刪除最老插入的節點
    void afterNodeRemoval(Node<K,V> p) { }//移除元素之後呼叫,更新雙向連結串列
  • 1
  • 2
  • 3
  • 4
    //回撥函式,新節點插入之後回撥 , 根據evict 和   判斷是否需要刪除最老插入的節點。如果實現LruCache會用到這個方法。
    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        //LinkedHashMap 預設返回false 則不刪除節點
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false