一、常見的記憶體淘汰演算法
-
FIFO 先進先出
-
在這種淘汰演算法中,先進⼊快取的會先被淘汰
-
命中率很低
-
-
LRU
-
-
Least recently used,最近最少使⽤get
-
-
-
根據資料的歷史訪問記錄來進⾏淘汰資料,其核⼼思想是“如果資料最近被訪問過,那麼將來被訪問的⼏率也更⾼”
-
-
-
LRU演算法原理剖析
-
-
LFU
- Least Frequently Used
-
演算法根據資料的歷史訪問頻率來淘汰資料,其核⼼思想是“如果資料過去被訪問多次,那麼將來被訪問的頻率也更⾼”
-
-
LFU演算法原理剖析
-
-
-
-
新加⼊資料插⼊到佇列尾部(因為引⽤計數為1)
-
-
-
-
-
佇列中的資料被訪問後,引⽤計數增加,佇列重新排序;
-
當需要淘汰資料時,將已經排序的列表最後的資料塊刪除。
-
-
- LFU的缺點
- 複雜度
- 儲存成本
- 尾部容易被淘汰
二、手寫LRU演算法實現
利用了LinkedHashMap雙向連結串列插入可排序
@Slf4j public class LRUCache<K, V> extends LinkedHashMap<K, V> { private int cacheSize; public LRUCache(int cacheSize) { super(16, 0.75f, true); this.cacheSize = cacheSize; } @Override public synchronized V get(Object key) { return super.get(key); } @Override public synchronized V put(K key, V value) { return super.put(key, value); } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { boolean f = size() > cacheSize; if (f) { log.info("LRUCache清除第三方金鑰快取Key:[{}]", eldest.getKey()); } return f; } public static void main(String[] args) { LRUCache<String, Object> cache = new LRUCache<>(5); cache.put("A","A"); cache.put("B","B"); cache.put("C","C"); cache.put("D","D"); cache.put("E","E"); System.out.println("初始化:" + cache.keySet()); System.out.println("訪問值:" + cache.get("C")); System.out.println("訪問C後:" + cache.keySet()); System.out.println("PUT F後:" + cache.put("F","F")); System.out.println(cache.keySet()); } }
main函式執行效果:
三、注意事項
LinkedHashMap有五個建構函式
//使用父類中的構造,初始化容量和載入因子,該初始化容量是指陣列大小。 public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } //一個引數的構造 public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } //無參構造 public LinkedHashMap() { super(); accessOrder = false; } //這個不用多說,用來接受map型別的值轉換為LinkedHashMap public LinkedHashMap(Map<? extends K, ? extends V> m) { super(m); accessOrder = false; } //真正有點特殊的就是這個,多了一個引數accessOrder。儲存順序,LinkedHashMap關鍵的引數之一就在這個, //true:指定迭代的順序是按照訪問順序(近期訪問最少到近期訪問最多的元素)來迭代的。 false:指定迭代的順序是按照插入順序迭代,也就是通過插入元素的順序來迭代所有元素 //如果你想指定訪問順序,那麼就只能使用該構造方法,其他三個構造方法預設使用插入順序。 public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
引數accessOrder。儲存順序,LinkedHashMap關鍵的引數之一就在這個, true:指定迭代的順序是按照訪問順序(近期訪問最少到近期訪問最多的元素)來迭代的。 false:指定迭代的順序是按照插入順序迭代,也就是通過插入元素的順序來迭代所有元素。
如果你想指定訪問順序,那麼就只能使用該構造方法,其他三個構造方法預設使用插入順序。
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
LinkedHashMap是非執行緒安全的,需要加互斥鎖解決併發問題。
四、思考
需要根據應用場景確定cacheSize大小,如果實際快取數量過小,會導致快取中的資料長期得不到重新整理,為防止這種或偶發情況的發生,可配合定時任務如起一個newSingleThreadScheduledExecutor,將上面儲存的value修改封裝為一個物件,裡面增加一個時間戳儲存,每次訪問實時更新,定時掃描該佇列將最近30分鐘未訪問的key刪除;還需增加一個初始進入佇列的歷史時間記錄,將超過1小時的資料清除。