LinkedHashMap

JDK版本jdk1.8.0_191 1

從繼承體系的角度,LinkedHashMap是HashMap的子類

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

內部維護一個雙向鏈表,維護插入順序或者LRU順序。

/**
* The head (eldest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> head;

/**
* The tail (youngest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> tail;

還有一個字段爲accessOrder表示的是鏈表維護的是插入順序還是LRU順序
默認false表示維護插入順序,而true則表示LRU順序。

final boolean accessOrder;

那麼它內部是怎麼維護鏈表的順序的呢?我們先回顧一下HashMap源碼解讀中的put,get方法,在putVal方法中有兩個函數調用

afterNodeAccess(e);
afterNodeInsertion(evict);

從字面的意思上可以知道,afterNodeAccess是節點被訪問後的函數調用,afterNodeInsertion是節點被插入後的函數調用;而這兩個函數實際上在HashMap中是空實現
實際上,類似的函數總共有3個,它們都是空實現

// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

HashMap函數中對3個函數註釋可以知道,這3個函數是空實現,並且它們是一種回調機制,特地提供給LinkedHashMap來後序回調用的;

所以LinkedHashMap繼承自HashMap,一定重寫了HashMap的這3個方法。

// 移除節點後調用,簡單地移除就行
void afterNodeRemoval(Node<K,V> e) { // unlink
    LinkedHashMap.Entry<K,V> p =
        (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    p.before = p.after = null;
    if (b == null)
        head = a;
    else
        b.after = a;
    if (a == null)
        tail = b;
    else
        a.before = b;
}

// 插入節點後調用
// 當 removeEldestEntry() 方法返回 true 時會移除最晚的節點
// 也就是鏈表首部節點 first。

// removeEldestEntry() 默認爲 false,
// 如果需要讓它爲 true,需要繼承 LinkedHashMap 並且覆蓋這個方法的實現,
// 這在實現 LRU 的緩存中特別有用,通過移除最近最久未使用的節點,
// 從而保證緩存空間足夠,並且緩存的數據都是熱點數據。
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);
    }
}

// 節點被訪問後調用
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    // 如果accessOrder爲true,也就是維護LRU鏈表
    // 如果指定維護LRU,當前訪問的節點不在尾部
    // 則將此節點移動到尾部
    if (accessOrder && (last = tail) != e) {
        //              ↓
    	// ... -> b -> e(p) -> a -> ... 
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        // 首先after置爲空,表示自己會被移動到尾部
        p.after = null;
        
        // 將p在鏈表處中斷了的位置接起來
        if (b == null) head = a;
        else b.after = a;
        
        if (a != null) a.before = b;
        else last = b;
        
        if (last == null) head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

這裏要注意第二個方法中對removeEldestEntry(first)方法的解讀。主要能夠實現減少LRU對空間的巨大消耗。下面是實現的一個簡單LRU。

// Reference to cyc2018
class LRUCache<K, V> extends LinkedHashMap<K, V> {

	private static final int MAX_ENTRIES = 3;
	
	protected boolean removeEldestEntry(Map.Entry eldest) {
		return size() > MAX_ENTRIES;
	}
	
	LRUCache() {
		super(MAX_ENTRIES, 0.75f, true);
	}
}
public static void main(String[] args) {
	LRUCache<Integer, String> cache = new LRUCache<>();
	cache.put(1, "a");
	cache.put(2, "b");
	cache.put(3, "c");
	cache.get(1);
	cache.put(4, "d");
	System.out.println(cache.keySet());
}
/** 運行結果
[3, 1, 4]
*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章