1. 程式人生 > >FIFO與LRU實現(Java)

FIFO與LRU實現(Java)

固定 blank per shu static 置換 ted clas als


一、概述

在學操作系統的時候,會接觸到頁面緩存調度算法。緩存不可能是無限大的,所以會涉及到一些置換策略,來保證緩存的命中率。常見的有: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
    時,則按照訪問順序對Map排序,那麽調用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)