LinkedHashMap實現LRU原理探究

LRU(least recently used)最近最少使用,是一種常用的頁面置換(緩存淘汰)算法。其他在java中,LinkedHashMap就實現了LRU。
那麼LinkedHashMap是如何實現的呢?
總體來說就是基於 HashMap+鏈表 ,使用HashMap保證查找效率是O(1),使用鏈表將所有節點連成一個隊列,保證順序性,也方便頭節點刪除和尾節點插入,插入或者刪除也是O(1)。

LinkedHashMap結構圖

1.定義

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

LinkedHashMap繼承了HashMap,因此查詢速度可以保證。

2.節點定義和字段

	/**
	* LinkedHashMap中使用的節點,增加before和after,用來連接成按次序的雙向鏈表
	*/
   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;

    /**
     * The iteration ordering method for this linked hash map: <tt>true</tt>
     * for access-order, <tt>false</tt> for insertion-order.
     * true時按照訪問順序連接
     * false時按照插入順序連接
     */
    final boolean accessOrder;

3. 構造函數

 	/**
     * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
     * with the default initial capacity (16) and load factor (0.75).
     * 默認是按照插入次序排序,初始容量16,負載因子0.75
     */
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

    /**
     * Constructs an empty <tt>LinkedHashMap</tt> instance with the
     * specified initial capacity, load factor and ordering mode.
     * 當我們需要實現LRU時,就可以使用該構造函數,設置accessOrder爲true
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

4.afterNodeAccess函數

在訪問節點(如get、replace)後會調用該函數,如果accessOrder爲true,即按照訪問次序排列,則會將剛剛訪問的節點移動到鏈表的尾部。

	void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            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;
        }
    }

5. afterNodeInsertion函數

在插入節點(如put)之後,會調用該函數。如果是按照插入順序排序,本來我以爲會在該函數中將插入的節點連到鏈表尾部。

 	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);
        }
    }

但是發現這裏沒有做將插入節點連到鏈表尾部的操作。後來發現在構造節點時,LinkedHashMap會在Node的構造函數中進行處理。

	//put時如果是新的key,會構造新的Node,可能是下面兩句代碼
	tab[i] = newNode(hash, key, value, null);
	p.next = newNode(hash, key, value, null);

	
 	Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        // 在這裏將你的node連到鏈表尾部
        linkNodeLast(p);
        return p;
    }

	// link at the end of list
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }

就這樣,利用HashMap和維護雙向鏈表,就可以實現LRU了。

發佈了90 篇原創文章 · 獲贊 46 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章