LRU算法
LRU算法定義:
LRU算法是指最近最少使用算法,意思是LRU認為最近使用過的數據,將來被訪問的概率會大,最近沒有被訪問的數據意味着以后剛問的概率小。
為何要用LRU算法:
1、我們的存儲空間是有限的,當存儲空間滿了之后,要刪除哪些數據呢,才能會時緩存的命中率高一些呢
2、LRU算法還是比較簡單的。
算法:
最常見的算法是使用一個鏈表來實現
1)將新數據插入到表頭
2)每當緩存命中時,將數據移動到表頭
3)當l存儲空間滿了的時候將鏈表尾部的數據刪除
缺點:鏈表實現,需要遍歷鏈表找到命中的數據
java中最簡單的LRU算法實現,就是利用jdk的LinkedHashMap。 LinkedHashMap底層就是用的HashMap加雙鏈表實現的,而且本身已經實現了按照訪問順序的存儲。此外,LinkedHashMap中本身就實現了一個方法removeEldestEntry用於判斷是否需要移除最不常讀取的數,方法默認是直接返回false,不會移除元素,所以需要重寫該方法。即當緩存滿后就移除最不常用的數。(通過覆蓋這個方法,加入一定的條件,滿足條件返回true。當put進新的值方法返回true時,便移除該map中最老的鍵和值。)
算法分析:
偶發性的、周期性的批量操作(可能不是熱點數據)會使臨時數據涌入緩存,擠出熱點數據,導致LRU熱點命中率急劇下降,緩存污染情況比較嚴重。
public class LRU<K,V> { private static final float hashLoadFactory = 0.75f; private LinkedHashMap<K,V> map; private int cacheSize; public LRU(int cacheSize) { this.cacheSize = cacheSize; int capacity = (int)Math.ceil(cacheSize / hashLoadFactory) + 1; map = new LinkedHashMap<K,V>(capacity, hashLoadFactory, true){ private static final long serialVersionUID = 1; @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > LRU.this.cacheSize; } }; } public synchronized V get(K key) { return map.get(key); } public synchronized void put(K key, V value) { map.put(key, value); } public synchronized void clear() { map.clear(); } public synchronized int usedSize() { return map.size(); } public void print() { for (Map.Entry<K, V> entry : map.entrySet()) { System.out.print(entry.getValue() + "--"); } System.out.println(); } }
LRU-K算法
LRU-K算法定義:
LRU-K中的K代表最近使用的次數,因此LRU可以認為是LRU-1。LRU-K的主要目的是為了解決LRU算法“緩存污染”的問題,其核心思想是將“最近使用過1次”的判斷標准擴展為“最近使用過K次”。
算法實現:
相比LRU,LRU-K需要多維護一個隊列,用於記錄所有緩存數據被訪問的歷史。只有當數據的訪問次數達到K次的時候,才將數據放入緩存。當需要淘汰數據時,LRU-K會淘汰第K次訪問時間距當前時間最大的數據。詳細實現如下:
1. 數據第一次被訪問,加入到訪問歷史列表;
2. 如果數據在訪問歷史列表里后沒有達到K次訪問,則按照一定規則(FIFO,LRU)淘汰;
3. 當訪問歷史隊列中的數據訪問次數達到K次后,將數據索引從歷史隊列刪除,將數據移到緩存隊列中,並緩存此數據,緩存隊列重新按照時間排序;
4. 緩存數據隊列中被再次訪問后,重新排序;
5. 需要淘汰數據時,淘汰緩存隊列中排在末尾的數據,即:淘汰“倒數第K次訪問離現在最久”的數據。