【集合框架】JDK1.8原始碼分析之LinkedHashMap(二)

leesf發表於2016-03-07

一、前言

  前面我們已經分析了HashMap的原始碼,已經知道了HashMap可以用在哪種場合,如果這樣一種情形,我們需要按照元素插入的順序來訪問元素,此時,LinkedHashMap就派上用場了,它儲存著元素插入的順序,並且可以按照我們插入的順序進行訪問。

二、LinkedHashMap用法

import java.util.Map;
import java.util.LinkedHashMap;

public class Test {
    public static void main(String[] args) {
        Map<String, String> maps = new LinkedHashMap<String, String>();
        maps.put("aa", "aa");
        maps.put("bb", "bb");
        maps.put("cc", "cc");
        
        for (Map.Entry entry : maps.entrySet()) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }
    }
}
View Code

說明:以上是展示LInkedHashMap簡單用法的一個示例,可以看到它確實按照元素插入的順序進行訪問,保持了元素的插入順序。更具體的使用者可以去參照API。

三、LinkedHashMap資料結構

  

  說明:LinkedHashMap會將元素串起來,形成一個雙連結串列結構。可以看到,其結構在HashMap結構上增加了連結串列結構。資料結構為(陣列 + 單連結串列 + 紅黑樹 + 雙連結串列),圖中的標號是結點插入的順序。

四、LinkedHashMap原始碼分析

  其實,在分析了HashMap的原始碼之後,我們來分析LinkedHashMap的原始碼就會容易很多,因為LinkedHashMap是在HashMap基礎上進行了擴充套件,我們需要注意的就是兩者不同的地方。

  4.1 類的繼承關係 

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

  說明:LinkedHashMap繼承了HashMap,所以HashMap的一些方法或者屬性也會被繼承;同時也實現了Map結構,關於HashMap類與Map介面,我們之前已經分析過,不再累贅。

  4.2 類的屬性

public class LinkedHashMap<K,V>  extends HashMap<K,V> implements Map<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);
        }
    }
    // 版本序列號
    private static final long serialVersionUID = 3801124242820219131L;

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

    // 連結串列尾結點
    transient LinkedHashMap.Entry<K,V> tail;

    // 訪問順序
    final boolean accessOrder;
}
View Code

  說明:由於繼承HashMap,所以HashMap中的非private方法和欄位,都可以在LinkedHashMap直接中訪問。

  4.3 類的建構函式

  1. LinkedHashMap(int, float)型建構函式

public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
}
View Code

  說明:總是會在建構函式的第一行呼叫父類建構函式,使用super關鍵字,accessOrder預設為false,access為true表示之後訪問順序按照元素的訪問順序進行,即不按照之前的插入順序了,access為false表示按照插入順序訪問。

  2. LinkedHashMap(int)型建構函式

public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
}
View Code

  3. LinkedHashMap()型建構函式

public LinkedHashMap() {
        super();
        accessOrder = false;
}
View Code

  4. LinkedHashMap(Map<? extends K, ? extends V>)型建構函式

public LinkedHashMap(Map<? extends K, ? extends V> m) {
    super();
    accessOrder = false;
    putMapEntries(m, false);
}
View Code

  說明:putMapEntries是呼叫到父類HashMap的函式

  5. LinkedHashMap(int, float, boolean)型建構函式

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

  說明:可以指定accessOrder的值,從而控制訪問順序。

  4.4 類的重要函式分析

  1. newNode函式

// 當桶中結點型別為HashMap.Node型別時,呼叫此函式
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    // 生成Node結點
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    // 將該結點插入雙連結串列末尾
    linkNodeLast(p);
    return p;
}
View Code

  說明:此函式在HashMap類中也有實現,LinkedHashMap重寫了該函式,所以當實際物件為LinkedHashMap,桶中結點型別為Node時,我們呼叫的是LinkedHashMap的newNode函式,而非HashMap的函式,newNode函式會在呼叫put函式時被呼叫。可以看到,除了新建一個結點之外,還把這個結點連結到雙連結串列的末尾了,這個操作維護了插入順序。

  其中LinkedHashMap.Entry繼承自HashMap.Node 

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

  說明:在HashMap.Node基礎上增加了前後兩個指標域,注意,HashMap.Node中的next域也存在。

  2. newTreeNode函式

// 當桶中結點型別為HashMap.TreeNode時,呼叫此函式
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
    // 生成TreeNode結點
    TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
    // 將該結點插入雙連結串列末尾
    linkNodeLast(p);
    return p;
}
View Code

  說明:當桶中結點型別為TreeNode時候,插入結點時呼叫的此函式,也會連結到末尾。

  3. afterNodeAccess函式  

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    // 若訪問順序為true,且訪問的物件不是尾結點
    if (accessOrder && (last = tail) != e) {
        // 向下轉型,記錄p的前後結點
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        // p的後結點為空
        p.after = null;
        // 如果p的前結點為空
        if (b == null)
            // a為頭結點
            head = a;
        else // p的前結點不為空
            // b的後結點為a
            b.after = a;
        // p的後結點不為空
        if (a != null)
            // a的前結點為b
            a.before = b;
        else // p的後結點為空
            // 後結點為最後一個結點
            last = b;
        // 若最後一個結點為空
        if (last == null)
            // 頭結點為p
            head = p;
        else { // p鏈入最後一個結點後面
            p.before = last;
            last.after = p;
        }
        // 尾結點為p
        tail = p;
        // 增加結構性修改數量
        ++modCount;
    }
}
View Code

  說明:此函式在很多函式(如put)中都會被回撥,LinkedHashMap重寫了HashMap中的此函式。若訪問順序為true,且訪問的物件不是尾結點,則下面的圖展示了訪問前和訪問後的狀態,假設訪問的結點為結點3

  

  說明:從圖中可以看到,結點3連結到了尾結點後面。

  4. transferLinks函式 

// 用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;
}
View Code

  說明:此函式用dst結點替換結點,示意圖如下

  

  說明:其中只考慮了before與after域,並沒有考慮next域,next會在呼叫tranferLinks函式中進行設定。

  5. containsValue函式  

public boolean containsValue(Object value) {
    // 使用雙連結串列結構進行遍歷查詢
    for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
        V v = e.value;
        if (v == value || (value != null && value.equals(v)))
            return true;
    }
    return false;
}
View Code

  說明:containsValue函式根據雙連結串列結構來查詢是否包含value,是按照插入順序進行查詢的,與HashMap中的此函式查詢方式不同,HashMap是使用按照桶遍歷,沒有考慮插入順序。

五、總結

  在HashMap的基礎上分析LinkedHashMap會容易很多,讀原始碼好處多多,有時間的話,園友們也可以讀讀原始碼,感受一下來自java設計者的智慧。謝謝觀看~

  

相關文章