LRU基本介紹及其實現方式
阿新 • • 發佈:2018-10-04
fifo隊列 ride ati implement 建立 復雜 介紹 .get util 中的1.2
。
2.1 組合方式
2.2 繼承
原文地址: http://note.youdao.com/noteshare?id=1abbeb1deee85f0203001e9bc34f65b4
參考
- LRU算法
- dubbo-cache
一.基本介紹
1.1 常見緩存淘汰算法及其實現思路
對於緩存,常見淘汰算法有3:
FIFO
: first in first out,先進先出,即假定剛剛加入的數據總會被訪問到;LRU
:least recently used,最近最少使用,判斷最近被使用的時間,假定未被使用的時間越久就不可能在被使用;LFU
:least frequently used,數據使用次數最少的,優先被淘汰。
對於FIFO算法,即caffeine
expireAfterWrite
方法,僅僅在數據插入時FIFO即可,LRU算法則在調用get()方法時再將數據重新插入即可。LFU
則將數據根據調用次數對數據進行排序。
1.2 LRU
不同版本介紹
LRU
分為 LRU
、LRU-K(2)
、two queues(2q)
和multi queue(MQ)
四個版本。命中率、代價和復雜度對比如下:
對比項目 | 排序 |
---|---|
命中率 | LRU-2 > MQ > 2Q > LRU |
復雜度 | 同上 |
時空復雜度/代價 | 同上 |
1)LRU
LRU least recently used 最近最少使用。根據數據的訪問歷史淘汰數據,實現是建立在鏈表上的。新插入和被訪問的節點放在表頭,刪除時從鏈尾開始
least recently used 最近最少使用在存在熱點數據時命中率高。但是缺點如下:
- 鎖力度比較粗,比如
ConcurrentHashMap
的分段鎖寫入性能好; - 由於LRU給新加入的節點放進了鏈表頭部—假定其擁有很大的quan zhi權值,因此如果出現周期性數據並且數據大小接近緩存時,則會汙染緩存—將有效緩存數據全部擠出緩存。
2)LRU-K
相比LRU,LRU-K需要維護一個訪問歷史隊列,用於記錄所有緩存數據被反問歷史,只有數據被反問次數達到k時,才將數據放進緩存數據隊列。
- 數據第一次被訪問時,加入“訪問歷史表”;
- 數據在“訪問歷史表”中按照FIFO或者LRU算法淘汰;
- 數據在“訪問歷史表”中被訪問k次後:將數據引用從“訪問歷史表”移至“緩存隊列表”中,並且按照時間排序—就是新加入或者被訪問的數據放在表頭
- 綜合考量LRU-2為最優選擇,雖然跟大的K值會讓命中率更高,但是適應性差,需要大量訪問才能將數據從“歷史隊列”清除。
優缺點:
- 相比超時緩存,LRU的鏈表結構決定其寫入性能受阻;
- 因為要維護沒有放入緩存的對象,因此比LRU占用更多的內存;
3) two queue
同樣是兩個隊列-第一個隊列使用FIFO算法。
- 新訪問的數據放進FIFO隊列;
- FIFO隊列數據沒有在被訪問則被淘汰;有則將數據插入到LRU隊列頭部,再次被訪問是則再將數據移動到隊列頭部;
- 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基本介紹及其實現方式