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


一、前言

  前面我們已經分析了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設計者的智慧。謝謝觀看~

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM