一、前言
前面我們已經分析了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()); } } }
說明:以上是展示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; }
說明:由於繼承HashMap,所以HashMap中的非private方法和字段,都可以在LinkedHashMap直接中訪問。
4.3 類的構造函數
1. LinkedHashMap(int, float)型構造函數

public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; }
說明:總是會在構造函數的第一行調用父類構造函數,使用super關鍵字,accessOrder默認為false,access為true表示之后訪問順序按照元素的訪問順序進行,即不按照之前的插入順序了,access為false表示按照插入順序訪問。
2. LinkedHashMap(int)型構造函數

public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; }
3. LinkedHashMap()型構造函數

public LinkedHashMap() { super(); accessOrder = false; }
4. LinkedHashMap(Map<? extends K, ? extends V>)型構造函數

public LinkedHashMap(Map<? extends K, ? extends V> m) { super(); accessOrder = false; putMapEntries(m, false); }
說明:putMapEntries是調用到父類HashMap的函數
5. LinkedHashMap(int, float, boolean)型構造函數

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
說明:可以指定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; }
說明:此函數在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); } }
說明:在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; }
說明:當桶中結點類型為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; } }
說明:此函數在很多函數(如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; }
說明:此函數用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; }
說明:containsValue函數根據雙鏈表結構來查找是否包含value,是按照插入順序進行查找的,與HashMap中的此函數查找方式不同,HashMap是使用按照桶遍歷,沒有考慮插入順序。
五、總結
在HashMap的基礎上分析LinkedHashMap會容易很多,讀源碼好處多多,有時間的話,園友們也可以讀讀源碼,感受一下來自java設計者的智慧。謝謝觀看~