1. 程式人生 > 實用技巧 >LinkedHashMap 實現LRU快取

LinkedHashMap 實現LRU快取

date: 2020-07-09 13:52:00
updated: 2020-07-21 17:40:00

LinkedHashMap 實現LRU快取

參考

LinkedHashMap是HashMap的子類,但是內部還有一個雙向連結串列維護鍵值對的順序,每個鍵值對既位於雜湊表中,也位於雙向連結串列中。LinkedHashMap支援兩種順序插入順序 、 訪問順序

插入順序:先新增的在前面,後新增的在後面。修改操作不影響順序
訪問順序:所謂訪問指的是get/put操作,對一個鍵執行get/put操作後,其對應的鍵值對會移動到連結串列末尾,所以最末尾的是最近訪問的,最開始的是最久沒有被訪問的,這就是訪問順序。

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)

其中引數accessOrder就是用來指定是否按訪問順序,如果為true,就是訪問順序。

LRU(Least Recently Used): 最近最少使用

public class LRUCache<K, V> extends LinkedHashMap<K, V> {

    private int maxEntries;

    public LRUCache(int maxEntries) {
        super(16, 0.75f, true);
        this.maxEntries = maxEntries;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxEntries;
    }
}

在LinkedHashMap新增元素後,會呼叫removeEldestEntry防範,傳遞的引數時最久沒有被訪問的鍵值對,如果方法返回true,這個最久的鍵值對就會被刪除。LinkedHashMap中的實現總返回false,該子類重寫後即可實現對容量的控制。

LRUCache<String,Object> cache = new LRUCache<>(3);
cache.put("a","abstract");
cache.put("b","basic");
cache.put("c","call");
cache.get("a");
cache.put("d","滴滴滴");
System.out.println(cache); // 輸出為:{c=call, a=abstract, d=滴滴滴}

相比HashMap,LinkedHashMap還實現了三個方法,當且僅當 accessOrder=True 時會被呼叫到

void afterNodeAccess(Node<K,V> p) { }  //訪問節點之後呼叫的方法
void afterNodeInsertion(boolean evict) { } //插入節點之後呼叫的方法
void afterNodeRemoval(Node<K,V> p) { }  //刪除節點之後呼叫的方法
在get()方法中呼叫afterNodeAccess()方法,具體作用是:在對節點進行訪問之後,會更新連結串列,將節點移動到連結串列的尾部,表示最近被訪問過。
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;
    }
}
LinkedHashMap的put()是呼叫的父類HashMap的put()方法
這個方法是在HashMap中的put()方法中被呼叫,在LinkedHashMap中被實現,具體作用是:在插入新節點後,因為快取不夠,需要刪除最近最少使用的節點。要成功呼叫這個方法,還需要使用者覆寫 removeEldestEntry(first) 方法
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成功呼叫removeNode()方法,刪除節點之後,會呼叫本方法
具體作用是:在removeNode方法中,只是刪除了HashMap中的節點,並沒有在連結串列中刪除。所以在removeNode中,回調了這個方法,將該節點從連結串列中刪除(這裡是刪除的頭結點,因為頭結點是最早進入或者最近最久未使用的)。
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;
}