FIFO與LRU實現(Java)
一、概述
在學操作系統的時候,會接觸到頁面緩存調度算法。緩存不可能是無限大的,所以會涉及到一些置換策略,來保證緩存的命中率。常見的有:FIFO、LRU、LFU、OPT策略等。
1、緩存置換算法
- FIFO:First In First Out,先進先出,和隊列保持一致。最先進來的最早出去。
- LRU:Least Recently Used,最近最少使用。總是淘汰最近沒有使用的。其核心思想是“如果數據最近被訪問過,那麽將來被訪問的幾率也更高”。
也就是說,淘汰最近一段時間內最長時間未訪問過的數據。根據程序局部性原理,剛被訪問的數據,可能馬上又要被訪問;而較長時間內沒有被訪問的數據,可能最近不會被訪問。 - LFU:Least Frequently Used,最近使用次數最少。即淘汰使用次數最少的。
- OPT:Optimal,最佳置換。置換以後永不再被訪問,或者在將來最遲才會被訪問的。該算法無法實現,通常作為衡量其他算法的標準。
2、緩存置換算法的要素
- (1)緩存不是無限大,需要有一個固定的大小來約束其大小
- (2)緩存滿後,再次插入需要替換掉某些元素,才能添加新元素
- (3)每次訪問完緩存,可能需要改變緩存元素的狀態,如元素順序的改變
3、Java LinkedHashMap簡介
先看下LinkedHashMap
的構造函數,三個參數分別為:初始化大小、裝載因子和訪問順序。
- 當參數
accessOrder = true
get()
方法後,會將這次訪問的元素移至鏈表尾部。不斷訪問可以形成按訪問順序排序的鏈表。 - 當參數
accessOrder = false
時,則按照插入順序對Map排序。先插入的元素放置在鏈表的首部,按照尾插入的方式維護鏈表。
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
根據鏈表中元素的順序可以分為:按插入順序的鏈表(默認,false),和按訪問順序的鏈表(調用get方法)。默認是按插入順序排序,如果指定按訪問順序排序,那麽調用get方法後,會將這次訪問的元素移至鏈表尾部,不斷訪問可以形成按訪問順序排序的鏈表。 可以重寫removeEldestEntry方法返回true值指定插入元素時移除最老的元素。
結論1:可以得出accessOrder = true
時,可以模仿出LRU的特性;accessOrder = false
時,可以模仿出FIFO的特性。即滿足緩存置換算法要素3。
這是LinkedHashMap中另外一個方法:移除最久的元素。當返回為false時,不會移除其中最久的元素。當返回true的時候,就會remove其中最久的元素。
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; }
結論2:由於緩存置換算法要素2,當緩存滿了之後,需通過返回true
刪除最久未被使用的元素。所以我們需要 重寫此方法來刪除緩存元素,達到緩存置換的要求。
當然,我們還需要滿足緩存置換算法要素1,就大功告成了。由於Java Map是自動擴容的,當其table.size() > Capacity * loadFactor
的時,會自動進行兩倍擴容。
結論:為了使緩存能固定大小,需要禁止Map的自動擴容。可將初始化大小設置為(cacheSize / loadFactor) + 1
,就可以在元素數目達到緩存大小時,不會自動擴容,達到緩存置換的要求。
二、實現簡單的FIFO緩存
1、繼承繼承LinkedHashMap
public class FIFOCache<K, V> extends LinkedHashMap<K, V> { private static int MAX_CACHE_SIZE; public FIFOCache(int maxCacheSize) { super((int) Math.ceil(maxCacheSize / 0.75) + 1, 0.75f, false); this.MAX_CACHE_SIZE = maxCacheSize; } @Override public boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_CACHE_SIZE; } }
2、根據LinkedHashMap重新實現
由於LinkedHashMap並非是線程安全的,我們可以僅利用LinkedHashMap的特性自己實現一個。
public class FIFOCache<K, V> { private static int MAX_CACHE_SIZE = 0; private final float LOAD_FACTORY = 0.75f; Map<K, V> map; public FIFOCache(int maxCacheSize) { this.MAX_CACHE_SIZE = maxCacheSize; // 根據 cacheSize 和 填充因子計算cache的容量 int capacity = (int) Math.ceil(MAX_CACHE_SIZE / LOAD_FACTORY) + 1; map = new LinkedHashMap<K, V>(capacity, LOAD_FACTORY, false) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_CACHE_SIZE; } }; } public synchronized void put(K key, V value) { map.put(key, value); } public synchronized V get(K key) { return map.get(key); } public synchronized void remove(K key) { map.remove(key); } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (Map.Entry<K, V> entry : map.entrySet()) { sb.append(entry.getKey()).append("=") .append(entry.getValue()).append(" "); } return sb.toString(); } }
三、實現簡單的LRU緩存
1、繼承LinkedHashMap
和FIFO的實現基本一致,只需要將accessOrder = false
。
public class Cache<K, V> extends LinkedHashMap<K, V> { private static int MAX_CACHE_SIZE; public Cache(int maxCacheSize) { super((int) Math.ceil(maxCacheSize / 0.75) + 1, 0.75f, true); this.MAX_CACHE_SIZE = maxCacheSize; } @Override public boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_CACHE_SIZE; // 需要刪除最久的元素 } }
2、根據LinkedHashMap重新實現
同樣,由於LinkedHashMap並非是線程安全的,我們可以僅利用LinkedHashMap的特性自己實現一個。
和FIFO的實現基本一致,只需要將accessOrder = false
。
public class LruCache<K, V> { private static int MAX_CACHE_SIZE = 0; private final float LOAD_FACTORY = 0.75f; Map<K, V> map; public LruCache(int maxCacheSize) { this.MAX_CACHE_SIZE = maxCacheSize; // 根據 cacheSize 和 填充因子計算cache的容量 int capacity = (int) Math.ceil(MAX_CACHE_SIZE / LOAD_FACTORY) + 1; map = new LinkedHashMap<K, V>(capacity, LOAD_FACTORY, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_CACHE_SIZE; } }; } public synchronized void put(K key, V value) { map.put(key, value); } public synchronized V get(K key) { return map.get(key); } public synchronized void remove(K key) { map.remove(key); } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (Map.Entry<K, V> entry : map.entrySet()) { sb.append(entry.getKey()).append("=") .append(entry.getValue()).append(" "); } return sb.toString(); } }
轉載:https://www.jianshu.com/p/33e572da4b58
FIFO與LRU實現(Java)