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