1. 程式人生 > >LRU基本介紹及其實現方式

LRU基本介紹及其實現方式

fifo隊列 ride ati implement 建立 復雜 介紹 .get util

原文地址: http://note.youdao.com/noteshare?id=1abbeb1deee85f0203001e9bc34f65b4
參考

  • LRU算法
  • dubbo-cache

一.基本介紹

1.1 常見緩存淘汰算法及其實現思路

對於緩存,常見淘汰算法有3:

  1. FIFO: first in first out,先進先出,即假定剛剛加入的數據總會被訪問到
  2. LRU:least recently used,最近最少使用,判斷最近被使用的時間,假定未被使用的時間越久就不可能在被使用
  3. LFU:least frequently used,數據使用次數最少的,優先被淘汰

對於FIFO算法,即caffeine

中的expireAfterWrite方法,僅僅在數據插入時FIFO即可,LRU算法則在調用get()方法時再將數據重新插入即可LFU則將數據根據調用次數對數據進行排序

1.2 LRU不同版本介紹

LRU分為 LRULRU-K(2)two queues(2q)multi queue(MQ)四個版本。命中率、代價和復雜度對比如下:

對比項目 排序
命中率 LRU-2 > MQ > 2Q > LRU
復雜度 同上
時空復雜度/代價 同上
1)LRU

LRU least recently used 最近最少使用。根據數據的訪問歷史淘汰數據,實現是建立在鏈表上的。新插入和被訪問的節點放在表頭,刪除時從鏈尾開始

技術分享圖片

least recently used 最近最少使用在存在熱點數據時命中率高。但是缺點如下:

  1. 鎖力度比較粗,比如ConcurrentHashMap的分段鎖寫入性能好;
  2. 由於LRU給新加入的節點放進了鏈表頭部—假定其擁有很大的quan zhi權值,因此如果出現周期性數據並且數據大小接近緩存時,則會汙染緩存—將有效緩存數據全部擠出緩存
2)LRU-K

相比LRU,LRU-K需要維護一個訪問歷史隊列,用於記錄所有緩存數據被反問歷史,只有數據被反問次數達到k時,才將數據放進緩存數據隊列

技術分享圖片

  1. 數據第一次被訪問時,加入“訪問歷史表”;
  2. 數據在“訪問歷史表”中按照FIFO或者LRU算法淘汰;
  3. 數據在“訪問歷史表”中被訪問k次後:將數據引用從“訪問歷史表”移至“緩存隊列表”中,並且按照時間排序—就是新加入或者被訪問的數據放在表頭
  4. 綜合考量LRU-2為最優選擇,雖然跟大的K值會讓命中率更高,但是適應性差,需要大量訪問才能將數據從“歷史隊列”清除

優缺點:

  • 相比超時緩存,LRU的鏈表結構決定其寫入性能受阻;
  • 因為要維護沒有放入緩存的對象,因此比LRU占用更多的內存;
3) two queue

同樣是兩個隊列-第一個隊列使用FIFO算法

技術分享圖片

  1. 新訪問的數據放進FIFO隊列;
  2. FIFO隊列數據沒有在被訪問則被淘汰;有則將數據插入到LRU隊列頭部,再次被訪問是則再將數據移動到隊列頭部;
  3. LRU隊列淘汰末尾的數據。

性能:
2Q性能和命中率同LRU-2,但是 2Q會減少一次從原始存儲讀取數據或者計算數據的操作

4)Multi-Queue

MQ算法根據數據訪問頻次將數據放進訪問優先級不同的多個隊列,訪問時優先訪問優先級比較高的隊列。如圖:
技術分享圖片

註意點:

  • 新插入的數據放到優先級最低的Q0,每個隊列按照LRU算法管理數據;
  • 當數據訪問次達到一定次數時,將數據移至更高優先級隊列。 當數據在指定時間未被訪問時,需要降低優先級;
  • Q-history記錄了從緩存中淘汰的數據,並且記錄數據引用和被調用次數,如果數據在Q-history中被重新訪問,則計算其優先級,移至相應隊列頭部;
  • 由數據降低優先級策略可知,MQ需要記錄並定時掃描數據的最近被訪問時間,因此代價比LRU高。

二.實現方式

2.1 組合方式linkedHashMap

因為java的單根繼承,因此組合應該優於繼承,便於擴展

public class LruCache<K,V> implements Cache<K,V> {
    private final Map<K,V> cache;

    public LruCache(final int maxSize){
        cache=new LinkedHashMap<K,V>(maxSize){
            //返回值表示是否進行移除操作,此方法在節點插入操作中被調用,如putVal、compute、computeIfAbsent、merge
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return this.size()>maxSize;
            }
        };
    }

    @Override
    public V get(K key) {
        return cache.get(key);
    }

    @Override
    public void put(K key, V value) {
        cache.put(key,value);
    }

    @Override
    public boolean remove(K key) {
        //remove返回之前與key綁定的value—如果不為null,表示移除的key對應的valu不為null;
        // 為null,返回false表示沒有與key對應的value
        return cache.remove(key)!=null;
    }
}
2.2 繼承LinkedHashMap

重載removeEldestEntry方法即可。想要線程安全則使用synchronized修飾方法即可。

import java.util.LinkedHashMap;
import java.util.Map.Entry;

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private static final long serialVersionUID = 1L;
    protected int maxElements;

    public LRUCache(final int maxSize) {
        super(maxSize, 0.75F, true);
        this.maxElements = maxSize;
    }

    /*
     * 返回值表示是否進行數據移除
     */
    @Override
    protected boolean removeEldestEntry(Entry<K, V> eldest) {
        return (size() > this.maxElements);
    }
}

LRU基本介紹及其實現方式