上一篇文章分析了HashMap的原理,有網友留言想看LinkedHashMap分析,今天它來了。java
LinkedHashMap是HashMap的子類,在原有HashMap數據結構的基礎上,它還維護着一個雙向鏈表連接全部entry,這個鏈表定義了迭代順序,一般是數據插入的順序。面試
上圖我只畫了鏈表,其實紅黑樹節點也是同樣的,只是節點類型不同而已緩存
也就是說咱們遍歷LinkedHashMap的時候,是從head指針指向的節點開始遍歷,一直到tail指向的節點。bash
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);
}
// 雙向鏈表頭節點
transient LinkedHashMap.Entry<K,V> head;
// 雙向鏈表尾節點
transient LinkedHashMap.Entry<K,V> tail;
// 指定遍歷LinkedHashMap的順序,true表示按照訪問順序,false表示按照插入順序,默認爲false
final boolean accessOrder;
}
}
複製代碼
從LinkedHashMap的定義裏面能夠看到它單獨維護了一個雙向鏈表,用於記錄元素插入的順序。這也是爲何咱們打印LinkedHashMap的時候能夠按照插入順序打印的支撐。而accessOrder屬性則指明瞭進行遍歷時是按照什麼順序進行訪問,咱們能夠經過它的構造方法本身指定順序。數據結構
public LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
複製代碼
當accessOrder=true,訪問順序的輸出是什麼意思呢?來看下下面的一段代碼ide
LinkedHashMap<Integer,Integer> map = new LinkedHashMap<>(8, 0.75f, true);
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
map.get(2);
System.out.println(map);
複製代碼
輸出結果是ui
{1=1, 3=3, 2=2}
複製代碼
能夠看到get了的數據被放到了雙向鏈表尾部,也就是按照了訪問時間進行排序,這就是訪問順序的含義。this
在插入的時候LinkedHashMap複寫了HashMap的newNode以及newTreeNode方法,而且在方法內部更新了雙向鏈表的指向關係。spa
同時插入的時候調用了afterNodeAccess()方法以及afterNodeInsertion()方法,在HashMap中這兩個方法是空實現,而在LinkedHashMap中則有具體實現,這兩個方法也是專門給LinkedHashMap進行回調處理的。指針
afterNodeAccess()方法中若是accessOrder=true時會移動節點到雙向鏈表尾部。當咱們在調用map.get()方法的時候若是accessOrder=true也會調用這個方法,這就是爲何訪問順序輸出時訪問到的元素移動到鏈表尾部的緣由。
接下來來看看afterNodeInsertion()的實現
// evict若是爲false,則表處於建立模式,當咱們new HashMap(Map map)的時候就處於建立模式
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
// removeEldestEntry 老是返回false,因此下面的代碼不會執行。
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
複製代碼
看到這裏我有一個想法,能夠經過LinkedHashMap來實現LRU(Least Recently Used,即近期最少使用),只要知足條件,就刪除head節點。
public class LRUCache<K,V> extends LinkedHashMap<K,V> {
private int cacheSize;
public LRUCache(int cacheSize) {
super(16,0.75f,true);
this.cacheSize = cacheSize;
}
/**
* 判斷元素個數是否超過緩存容量
*/
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > cacheSize;
}
}
複製代碼
就這樣一個簡單的LRU Cache就實現了,之後面試官若是喊你給它實現一個LRU,你就這樣寫給他,若是他讓你換一種方式,你就用鏈表使用一樣的思惟給他實現一個,而後你就能夠收割offer了。
對於刪除,LinkedHashMap也一樣是在HashMap的刪除邏輯完成後,調用了afterNodeRemoval這個回調方法來更正鏈表指向關係。
其實你只要看了上一篇文章不再怕面試官問我JDK8 HashMap,再記得LinkedHashMap只是多維護了一個雙向鏈表以後,再看LinkedHashMap中關於鏈表操做的代碼就很是簡單了。