LinkedHashMap內部維護了一個雙向鏈表,能保證元素按插入的順序訪問,也能以訪問順序訪問,能夠用來實現LRU緩存策略。java
LinkedHashMap能夠當作是 LinkedList + HashMap。node
LinkedHashMap繼承HashMap,擁有HashMap的全部特性,而且額外增長的按必定順序訪問的特性數組
咱們知道HashMap使用(數組 + 單鏈表 + 紅黑樹)的存儲結構,經過上面的繼承體系,咱們知道LinkedHashMap繼承了Map,因此它的內部也有這三種結構,可是它還額外添加了一種「雙向鏈表」的結構存儲全部元素的順序。緩存
添加刪除元素的時候須要同時維護在HashMap中的存儲,也要維護在LinkedList中的存儲,因此性能上來講會比HashMap稍慢。ide
/** * 雙向鏈表頭節點 */ transient LinkedHashMap.Entry<K,V> head; /** * 雙向鏈表尾節點 */ transient LinkedHashMap.Entry<K,V> tail; /** * 是否按訪問順序排序 */ final boolean accessOrder;
1.head
雙向鏈表的頭節點,舊數據存在頭節點。性能
2.tail
雙向鏈表的尾節點,新數據存在尾節點。this
3.accessOrder
是否須要按訪問順序排序,若是爲false則按插入順序存儲元素,若是是true則按訪問順序存儲元素。spa
// 位於LinkedHashMap中 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中 static class Node<K, V> implements Map.Entry<K, V> { final int hash; final K key; V value; Node<K, V> next; }
存儲節點,繼承自HashMap的Node類,next用於單鏈表存儲於桶中,before和after用於雙向鏈表存儲全部元素。code
public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } public LinkedHashMap() { super(); accessOrder = false; } public LinkedHashMap(Map<? extends K, ? extends V> m) { super(); accessOrder = false; putMapEntries(m, false); } public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
前四個構造方法accessOrder都等於false,說明雙向鏈表是按插入順序存儲元素。blog
最後一個構造方法accessOrder從構造方法參數傳入,若是傳入true,則就實現了按訪問順序存儲元素,這也是實現LRU緩存策略的關鍵。
在節點插入以後作些什麼,在HashMap中的putVal()方法中被調用,能夠看到HashMap中這個方法的實現爲空。
void afterNodeInsertion(boolean evict) { // possibly remove eldest LinkedHashMap.Entry<K,V> first; if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } } protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; }
在節點訪問以後被調用,主要在put()已經存在的元素或get()時被調用,若是accessOrder爲true,調用這個方法把訪問到的節點移動到雙向鏈表的末尾。
void afterNodeAccess(Node<K,V> e) { // move node to last LinkedHashMap.Entry<K,V> last; // 若是accessOrder爲true,而且訪問的節點不是尾節點 if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; // 把p節點從雙向鏈表中移除 p.after = null; if (b == null) head = a; else b.after = a; if (a != null) a.before = b; else last = b; // 把p節點放到雙向鏈表的末尾 if (last == null) head = p; else { p.before = last; last.after = p; } // 尾節點等於p tail = p; ++modCount; } }
在節點被刪除以後調用的方法。
void afterNodeRemoval(Node<K,V> e) { // unlink LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; // 把節點p從雙向鏈表中刪除。 p.before = p.after = null; if (b == null) head = a; else b.after = a; if (a == null) tail = b; else a.before = b; }
經典的把節點從雙向鏈表中刪除的方法。
獲取元素。
public V get(Object key) { Node<K,V> e; if ((e = getNode(hash(key), key)) == null) return null; if (accessOrder) afterNodeAccess(e); return e.value; }
若是查找到了元素,且accessOrder爲true,則調用afterNodeAccess()方法把訪問的節點移到雙向鏈表的末尾。
LRU,Least Recently Used,最近最少使用,也就是優先淘汰最近最少使用的元素。
若是使用LinkedHashMap,咱們把accessOrder設置爲true就差很少能實現這個策略了:
package com.coolcoding.code; import java.util.LinkedHashMap; import java.util.Map; public class LRUTest { public static void main(String[] args) { // 建立一個只有5個元素的緩存 LRU<Integer, Integer> lru = new LRU<>(5, 0.75f); lru.put(1, 1); lru.put(2, 2); lru.put(3, 3); lru.put(4, 4); lru.put(5, 5); lru.put(6, 6); lru.put(7, 7); System.out.println(lru.get(4)); lru.put(6, 666); // 輸出: {3=3, 5=5, 7=7, 4=4, 6=666} // 能夠看到最舊的元素被刪除了 // 且最近訪問的4被移到了後面 System.out.println(lru); } } class LRU<K, V> extends LinkedHashMap<K, V> { // 保存緩存的容量 private int capacity; public LRU(int capacity, float loadFactor) { super(capacity, loadFactor, true); this.capacity = capacity; } /** * 重寫removeEldestEntry()方法設置什麼時候移除舊元素 * @param eldest * @return */ @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { // 當元素個數大於了緩存的容量, 就移除元素 return size() > this.capacity; } }