java集合類六-LinkedHashMap和LRU

目錄

 

1 LinkedHashMap有序

2 LRU機制介紹

3 LinkedHashMap原理

3.1 put方法

3.2 get方法


1 LinkedHashMap有序

LinkedHashMap是有序的原因是LinkedHashMap的內部了Entry在繼承了HashMap.Node基礎上新增了前驅和後繼屬性。

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

LRU就是基於這個特性實現的

2 LRU機制介紹

何爲LRU?基Least Recent Used,最近最少訪問,通常用於緩存維護。當緩存服務器內存或別的資源不足時,把那些不經常訪問的緩存數據給刪除掉。
我們看一下構造方法

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

這裏重點關注第三個參數accessOrder,默認false,遍歷元素的時候按照插入順序;如果爲true則按照訪問順序。下面簡單實現衣蛾LRU緩存

public class LruCache {

    private LinkedHashMap<String, String> map;

    private int cacheSize;

    public LruCache(int cacheSize) {
        this.cacheSize = cacheSize;
        /**
         * 第三個參數accessOrder,默認false,遍歷元素的時候按照插入順序;如果爲true則按照訪問順序
         */
        map = new LinkedHashMap<String, String>(cacheSize, 0.75F, true) {
            private static final long serialVersionUID = -6650855993931928707L;

            /**
             * 當map中的元素大於閾值時移除最老的元素
             * @param eldest
             * @return
             */
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
                boolean result = size() > cacheSize;
                if (result) {
                    System.out.println("remove eldest entry:" + eldest);
                }
                return result;
            }
        };
    }

    public void put(String k, String v) {
        map.put(k, v);
    }

    public String get(String k) {
        return map.get(k);
    }

    public void print() {
        System.out.println(map);
    }
}

測試代碼:

public static void main(String[] args) {
    LruCache lruCache = new LruCache(5);
    for (int i = 0; i < 5; i ++) {
        lruCache.put(i + "", i + "");
    }
    lruCache.print();
    lruCache.put("6", "6");
    lruCache.print();
    System.out.println("get key 1");
    lruCache.get("1");
    lruCache.print();
    lruCache.put("7", "7");
    lruCache.print();
}

輸出:
{0=0, 1=1, 2=2, 3=3, 4=4}
remove eldest entry:0=0
{1=1, 2=2, 3=3, 4=4, 6=6}
get key 1
{2=2, 3=3, 4=4, 6=6, 1=1}
remove eldest entry:2=2
{3=3, 4=4, 6=6, 1=1, 7=7}
可以看到添加緩存元素6後會把最先添加的緩存元素0給移除掉。
訪問緩存元素1後會把1放到末尾。這樣就實現了LRU策略緩存

3 LinkedHashMap原理

3.1 put方法

put方法是繼承的父類HashMap方法,每次添加元素後會執行模板方法afterNodeInsertion

// put方法evict傳的是true
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中removeEldestEntry方法返回的是false,默認不刪除最老的元素,但是我們重寫了該方法當容量超過閾值時移除頭部元素

3.2 get方法

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

// 這裏實際上是把被訪問元素放到鏈表尾部,代碼比較簡單,不做太多註釋
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;
    }
}

這樣就實現了鏈表頭部是最早訪問的元素,緩存淘汰的時候優先淘汰頭部元素

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章