源碼閱讀(23):Java中其它主要的Map結構——LinkedHashMap容器(上)

1、概述

LinkedHashMap容器是Java容器框架中從很早的版本就開始提供的(JDK 1.4+),該容器又被這樣認爲:“LinkedHashMap = HashMap + LinkedList”。LinkedHashMap容器的主要繼承體系如下圖所示:
在這裏插入圖片描述
LinkedHashMap容器繼承自HashMap容器,也就是說前者的基本結構和後者一致,在這樣的基本結構下LinkedHashMap容器提供了一個新的特性,就是保證容器內部各個結點可以以一種順序進行遍歷(迭代器支持):

  • 這個順序可以基於結點添加到容器的時間(insertion-order):也就是說先添加到容器的K-V鍵值對對象,在使用LinkedHashMap容器的迭代器進行遍歷時,將會首先被遍歷。這個遍歷順序和K-V鍵值對對象屬於哪一個桶結構,該桶結構具體是按照單向鏈表排列的,還是按照紅黑樹排列的都沒有關係。

  • 這個順序也可以是該結點在容器中最後一次被操作(讀操作或寫操作)的時間(access-order):當LinkedHashMap容器指定的K-V鍵值對對象被進行了操作(無論是修改還是讀取操作),它都會被重新排列到遍歷結果的最後。

以上兩種遍歷順序,完全基於LinkedHashMap容器實例化時的設定參數,我們首先來看一些實際的使用示例:

//......
// 默認的LinkedHashMap容器將採用K-V鍵值對的添加順序(access-order),作爲遍歷順序
LinkedHashMap<String, String> accessOrderMap = new LinkedHashMap<>();
accessOrderMap.put("key1", "value1");
accessOrderMap.put("key2", "value2");
accessOrderMap.put("key3", "value3");
accessOrderMap.put("key4", "value4");
accessOrderMap.put("key5", "value5");
accessOrderMap.put("key6", "value6");
accessOrderMap.put("key7", "value7");
accessOrderMap.put("key8", "value8");
Set<Entry<String, String>> accessOrderSets = accessOrderMap.entrySet();
System.out.println("//accessOrderSets===================第一次遍歷順序");
for (Entry<String, String> entry : accessOrderSets) {
  System.out.println("current entry : " + entry);
}
// 當對某一個鍵值對的信息進行修改時,不會引起遍歷順序的變化
accessOrderMap.put("key4", "value44");
// 這次遍歷順序將與上次遍歷順序一致
System.out.println("//accessOrderSets===================第二次遍歷順序");
for (Entry<String, String> entry : accessOrderSets) {
  System.out.println("current entry : " + entry);
}

// 如果使用以下實例化方式的話
// LinkedHashMap容器將採用K-V鍵值對的操作順序(insertion-order),作爲遍歷順序
LinkedHashMap<String, String> insertionOrderMap = new LinkedHashMap<>(16, 0.75f, true);
insertionOrderMap.put("key1", "value1");
insertionOrderMap.put("key2", "value2");
insertionOrderMap.put("key3", "value3");
insertionOrderMap.put("key4", "value4");
insertionOrderMap.put("key5", "value5");
insertionOrderMap.put("key6", "value6");
insertionOrderMap.put("key7", "value7");
insertionOrderMap.put("key8", "value8");
Set<Entry<String, String>> insertionOrderSets = insertionOrderMap.entrySet();
System.out.println("//insertionOrderSets===================第一次遍歷順序");
for (Entry<String, String> entry : insertionOrderSets) {
  System.out.println("current entry : " + entry);
}
// 當對某一個鍵值對的信息進行修改,就會引起遍歷順序的變化
insertionOrderMap.put("key4", "value44");
// 對某一個鍵值對信息進行讀取操作,同樣會引起遍歷順序的變化
insertionOrderMap.get("key7");
// 這次遍歷順序將與上次遍歷順序不一致
System.out.println("//insertionOrderSets===================第二次遍歷順序");
for (Entry<String, String> entry : insertionOrderSets) {
  System.out.println("current entry : " + entry);
}
//......

以下爲輸出結果:

//accessOrderSets===================第一次遍歷順序
current entry : key1=value1
current entry : key2=value2
current entry : key3=value3
current entry : key4=value4
current entry : key5=value5
current entry : key6=value6
current entry : key7=value7
current entry : key8=value8
//accessOrderSets===================第二次遍歷順序
current entry : key1=value1
current entry : key2=value2
current entry : key3=value3
current entry : key4=value44
current entry : key5=value5
current entry : key6=value6
current entry : key7=value7
current entry : key8=value8
//insertionOrderSets===================第一次遍歷順序
current entry : key1=value1
current entry : key2=value2
current entry : key3=value3
current entry : key4=value4
current entry : key5=value5
current entry : key6=value6
current entry : key7=value7
current entry : key8=value8
//insertionOrderSets===================第二次遍歷順序
current entry : key1=value1
current entry : key2=value2
current entry : key3=value3
current entry : key5=value5
current entry : key6=value6
current entry : key8=value8
current entry : key4=value44
current entry : key7=value7

以上代碼輸出結果的意義就不再贅述了。下面我們就來詳細LinkedHashMap容器的源碼原理,先從LinkedHashMap容器結點的結構開始介紹。

1.1、LinkedHashMap容器結點結構

在這裏插入圖片描述
如上圖所示,是LinkedHashMap容器中構造每個結點所使用的LinkedHashMap.Entry的繼承體系。從上圖我們就知道了LinkedHashMap.Entry繼承自HashMap.Node,再結合LinkedHashMap.Entry類源代碼中各屬性的描述,我們可以知道LinkedHashMap容器中每一個結點具有哪些屬性了。結點的定義代碼如下所示:

// HashMap.Node結點的屬性定義,上文已經介紹過了,這裏就不再進行贅述了
static class Node<K,V> implements Map.Entry<K,V> {
  final int hash;
  final K key;
  V value;
  // next屬性,保證了HashMap容器在進行紅黑樹到鏈表的轉換過程中,提高轉換效率
  Node<K,V> next;
}
// TreeNode結點的屬性在上文中也已經介紹過了
// 當HashMap中某個桶結構爲紅黑樹時,使用這樣的結點定義
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
  // 記錄了當前紅黑樹結點父結點
  TreeNode<K,V> parent; 
  // 記錄了當前紅黑樹結點的左兒子結點
  TreeNode<K,V> left;
  // 記錄了當前紅黑樹結點的右兒子結點
  TreeNode<K,V> right;
  // 這是紅黑樹結構中隱含的一個雙向鏈表結構
  // 該屬性記錄了單向鏈表向紅黑樹轉換後,記錄的前置結點
  TreeNode<K,V> prev;
  // 該屬性表示當前結點是紅色還是黑色
  boolean red;
}
static class Entry<K,V> extends HashMap.Node<K,V> {
  // LinkedHashMap容器中的結點可以互相引用連接起來,變成一個雙向鏈表
  // 該屬性記錄了當前LinkedHashMap容器中,雙向鏈表的前一個結點;
  Entry<K,V> before;
  // 該屬性記錄了當前LinkedHashMap容器中,雙向鏈表的後一個結點;
  Entry<K,V> after;
}

下圖進一步說明了LinkedHashMap容器中的每一個結點擁有的各個屬性,注意要分爲兩種情況考慮:如果當前LinkedHashMap容器中指定的位置(table數組的某個索引位)的桶結構存儲的是單向鏈表,那麼對應的結點結構如下圖所示:

在這裏插入圖片描述
如果LinkedHashMap容器中指定的桶位置存儲的是紅黑樹結構,那麼對應的結點結構如下圖所示:
在這裏插入圖片描述

1.2、LinkedHashMap容器整體構造

分析完LinkedHashMap容器中每個K-V鍵值對對象屬性的擴展後,我們再來看看LinkedHashMap容器定義層面上又做了哪些擴展。以下代碼片段示意了LinkedHashMap容器擴展的屬性內容:

// LinkedHashMap容器的基本屬性定義
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {
  // ......
  /**
   * 雙向鏈表的頭結點引用
   * The head (eldest) of the doubly linked list.
   */
  transient LinkedHashMap.Entry<K,V> head;
  /**
   * 雙向鏈表的尾結點應用,根據上文提到的LinkedHashMap容器的構造設定
   * 這裏的尾結點可能是最後添加到LinkedHashMap容器的結點,也可能是LinkedHashMap容器中被最新訪問的結點
   * The tail (youngest) of the doubly linked list.
   */
  transient LinkedHashMap.Entry<K,V> tail;
  /**
   * 該屬性表示LinkedHashMap容器中特有的雙向鏈表總結點的排序特點。
   * 如果爲false(默認爲false),將按照結點被添加到LinkedHashMap容器的順序進行排序;
   * 如果爲true,將按照結點最近被操作(修改操作或者讀取操作)的順序進行排序
   */
  final boolean accessOrder;
  // ......
}

除了以上LinkedHashMap容器的屬性定義外,由於LinkedHashMap容器和HashMap容器的繼承關係,前者當然也就具有了後者的屬性定義,例如HashMap容器中用於存儲桶結構的數組變量table。我們可以用以下示例圖描述一個LinkedHashMap容器的結點存儲效果:
在這裏插入圖片描述
通過LinkedHashMap容器中每個結點的before、after屬性形成的雙向鏈表,將串聯上容器中的所有結點;這些結點在雙向鏈表中的順序和這些結點處於哪一個桶結構中,桶結構本身是單向鏈表結構還是紅黑樹結構並沒有關係,有關係的只是這個結點代表的K-V鍵值對在時間維度上被添加到LinkedHashMap容器中的順序

通過LinkedHashMap容器層面的head屬性和tail屬性,保證了被串聯的結點可以跨越不同的桶結構。請注意根據LinkedHashMap容器的初始化設定,head屬性和tail屬性指向的節點是可能會發生變化的。

============
(接後文《源碼閱讀(24):Java中其它主要的Map結構——LinkedHashMap容器(下)》)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章