緩存算法(FIFO 、LRU、LFU三種算法的區別)
FIFO算法#
FIFO 算法是一種比較容易實現的算法。它的思想是先進先出(FIFO,隊列),這是最簡單、最公平的一種思想,即若是一個數據是最早進入的,那麼能夠認爲在未來它被訪問的可能性很小。空間滿的時候,最早進入的數據會被最先置換(淘汰)掉。html
FIFO 算法的描述:設計一種緩存結構,該結構在構造時肯定大小,假設大小爲 K,並有兩個功能:java
- set(key,value):將記錄(key,value)插入該結構。當緩存滿時,將最早進入緩存的數據置換掉。
- get(key):返回key對應的value值。
實現:維護一個FIFO隊列,按照時間順序將各數據(已分配頁面)連接起來組成隊列,並將置換指針指向隊列的隊首。再進行置換時,只需把置換指針所指的數據(頁面)順次換出,並把新加入的數據插到隊尾便可。算法
缺點:判斷一個頁面置換算法優劣的指標就是缺頁率,而FIFO算法的一個顯著的缺點是,在某些特定的時刻,缺頁率反而會隨着分配頁面的增長而增長,這稱爲Belady現象。產生Belady現象現象的緣由是,FIFO置換算法與進程訪問內存的動態特徵是不相容的,被置換的內存頁面每每是被頻繁訪問的,或者沒有給進程分配足夠的頁面,所以FIFO算法會使一些頁面頻繁地被替換和從新申請內存,從而致使缺頁率增長。所以,如今再也不使用FIFO算法。sql
LRU算法#
LRU(The Least Recently Used,最近最久未使用算法)是一種常見的緩存算法,在不少分佈式緩存系統(如Redis, Memcached)中都有普遍使用。數組
LRU算法的思想是:若是一個數據在最近一段時間沒有被訪問到,那麼能夠認爲在未來它被訪問的可能性也很小。所以,當空間滿時,最久沒有訪問的數據最早被置換(淘汰)。緩存
LRU算法的描述: 設計一種緩存結構,該結構在構造時肯定大小,假設大小爲 K,並有兩個功能:markdown
- set(key,value):將記錄(key,value)插入該結構。當緩存滿時,將最久未使用的數據置換掉。
- get(key):返回key對應的value值。
實現:最樸素的思想就是用數組+時間戳的方式,不過這樣作效率較低。所以,咱們能夠用雙向鏈表(LinkedList)+哈希表(HashMap)實現(鏈表用來表示位置,哈希表用來存儲和查找),在Java裏有對應的數據結構LinkedHashMap。數據結構
LInkedHashMap#
利用Java
的LinkedHashMap
用很是簡單的代碼來實現基於LRU算法的Cache功能分佈式
import java.util.LinkedHashMap; import java.util.Map; /** * 簡單用LinkedHashMap來實現的LRU算法的緩存 */ public class LRUCache<K, V> extends LinkedHashMap<K, V> { private int cacheSize; public LRUCache(int cacheSize) { super(16, (float) 0.75, true); this.cacheSize = cacheSize; } protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > cacheSize; } }
測試:post
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LRUCacheTest { private static final Logger log = LoggerFactory.getLogger(LRUCacheTest.class); private static LRUCache<String, Integer> cache = new LRUCache<>(10); public static void main(String[] args) { for (int i = 0; i < 10; i++) { cache.put("k" + i, i); } log.info("all cache :'{}'",cache); cache.get("k3"); log.info("get k3 :'{}'", cache); cache.get("k4"); log.info("get k4 :'{}'", cache); cache.get("k4"); log.info("get k4 :'{}'", cache); cache.put("k" + 10, 10); log.info("After running the LRU algorithm cache :'{}'", cache); } }
Output:
all cache :'{k0=0, k1=1, k2=2, k3=3, k4=4, k5=5, k6=6, k7=7, k8=8, k9=9}' get k3 :'{k0=0, k1=1, k2=2, k4=4, k5=5, k6=6, k7=7, k8=8, k9=9, k3=3}' get k4 :'{k0=0, k1=1, k2=2, k5=5, k6=6, k7=7, k8=8, k9=9, k3=3, k4=4}' get k4 :'{k0=0, k1=1, k2=2, k5=5, k6=6, k7=7, k8=8, k9=9, k3=3, k4=4}' After running the LRU algorithm cache :'{k1=1, k2=2, k5=5, k6=6, k7=7, k8=8, k9=9, k3=3, k4=4, k10=10}'
LFU算法#
LFU(Least Frequently Used ,最近最少使用算法)也是一種常見的緩存算法。
顧名思義,LFU算法的思想是:若是一個數據在最近一段時間不多被訪問到,那麼能夠認爲在未來它被訪問的可能性也很小。所以,當空間滿時,最小頻率訪問的數據最早被淘汰。
LFU 算法的描述:
設計一種緩存結構,該結構在構造時肯定大小,假設大小爲 K,並有兩個功能:
- set(key,value):將記錄(key,value)插入該結構。當緩存滿時,將訪問頻率最低的數據置換掉。
- get(key):返回key對應的value值。
算法實現策略:考慮到 LFU 會淘汰訪問頻率最小的數據,咱們須要一種合適的方法按大小順序維護數據訪問的頻率。LFU 算法本質上能夠看作是一個 top K 問題(K = 1),即選出頻率最小的元素,所以咱們很容易想到能夠用二項堆來選擇頻率最小的元素,這樣的實現比較高效。最終實現策略爲小頂堆+哈希表。