1. 程式人生 > >Java集合中的LinkedHashMap類

Java集合中的LinkedHashMap類

1.8 插入元素 p s 理解 ast ali sso ins 鏈表實現

jdk1.8.0_144

  本文閱讀最好先了解HashMap底層,可前往《Java集合中的HashMap類》。

  LinkedHashMap由於它的插入有序特性,也是一種比較常用的Map集合。它繼承了HashMap,很多方法都直接復用了父類HashMap的方法。本文將探討LinkedHashMap的內部實現,以及它是如何保證插入元素是按插入順序排序的。

  在分析前可以先思考下,既然是按照插入順序,並且以Linked-開頭,就很有可能是鏈表實現。如果純粹以鏈表實現,也不是不可以,LinkedHashMap內部維護一個鏈表,插入一個元素則把它封裝成Entry節點,並把它插入到鏈表尾部。功能可以實現,但這帶來的查找效率達到了O(n),顯然遠遠大於HashMap在沒有沖突的情況下O(1)的時間復雜度。這就絲毫不能體現出Map這種數據結構隨機存取快的優點。

  所以顯然,LinkedHashMap不可能只有一個鏈表來維護Entry節點,它極有可能維護了兩種數據結構:散列表+鏈表。

  為便於理解,將不會分析每個方法,會從插入開始分析LinkedHashMap的數據結構及實現。

  LinkedHashMap繼承了HashMap類,並且沒有重寫put方法,而是直接沿用了HashMap#put方法。有關HashMap#put已經在《Java集合中的HashMap類》有了較為詳細的介紹。從調用HashMap#put方法可知,它的插入過程和HashMap相同,也就是說它也一樣有著和HashMap相同的散列表結構。不過要小心盡管調用的是HashMap#put方法,但在這個方法中有一個方法是構造一個新節點newNode,這裏LinkedHashMap重寫了,所以調用的是LinkedHashMap#newNode,也正是這個方法實現了對LinkedHashMap鏈表的維護。

  忽略其余代碼,關鍵代碼在HashMap#putVal中tab[i] = newNode(hash, key, value, null),稍後再來查看LinkedHashMap#newNode方法。

  其過程先用圖例來說明。

技術分享圖片

  鏈表插入過程如下代碼所示:

1 //LinkedHashMap#newNode,構造一個新的節點
2 Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
3     LinkedHashMap.Entry<K,V> p =
4         new
LinkedHashMap.Entry<K,V>(hash, key, value, e); 5 linkNodeLast(p); 6 return p; 7 }  
 1 //LinkedHashMap#linkNodeLast,插入到鏈表尾部
 2 private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
 3     LinkedHashMap.Entry<K,V> last = tail;        //LinkedHashMap定義了tail尾指針和head頭指針,且鏈表為雙向鏈表
 4     tail = p;
 5     if (last == null)
 6         head = p;
 7     else {
 8 //雙向鏈表的插入
 9         p.before = last;
10         last.after = p;
11     }
12 }

  對於LinkedHashMap插入,散列表部分和HashMap一致,而雙向鏈表部分則是來一個就插到尾部,這樣就保證了保持插入順序。

  通過插入基本了解了LinkedHashMap的內部實現,get方法很簡單,同樣是計算出key的hash和對應散列表的下標即可。

  在LinkedHashMap還需要提到三個方法,這三個方法在HashMap中定義,但是並沒有具體實現,具體實現放到了LinkedHashMap中。

void afterNodeAccess(Node<K,V> p)

  此方法可以實現通過訪問順序排序,方法中如果定義accessOrder=true,則會將訪問(get)過的元素放到鏈表尾部。accessOrder設置可以通過構造方法傳遞。

void afterNodeInsertion(boolean evict)

  這個方法在LinkedHashMap並無意義,因為它調用的removeEldestEntry始終返回false,此時程序就會返回不會執行。但如果重寫了removeEldestEntry方法,則可以實現LRU(最近最少使用)緩存。

void afterNodeRemoval(Node<K,V> p)

  移除Map中的元素時調用,更新雙向鏈表。

這是一個能給程序員加buff的公眾號 技術分享圖片

Java集合中的LinkedHashMap類