緩存算法(FIFO 、LRU、LFU三種算法的區別)

FIFO算法

FIFO 算法是一種比較容易實現的算法。它的思想是先進先出(FIFO,隊列),這是最簡單、最公平的一種思想,即若是一個數據是最早進入的,那麼能夠認爲在未來它被訪問的可能性很小。空間滿的時候,最早進入的數據會被最先置換(淘汰)掉html

FIFO 算法的描述:設計一種緩存結構,該結構在構造時肯定大小,假設大小爲 K,並有兩個功能:java

  1. set(key,value):將記錄(key,value)插入該結構。當緩存滿時,將最早進入緩存的數據置換掉。
  2. get(key):返回key對應的value值。

實現:維護一個FIFO隊列,按照時間順序將各數據(已分配頁面)連接起來組成隊列,並將置換指針指向隊列的隊首。再進行置換時,只需把置換指針所指的數據(頁面)順次換出,並把新加入的數據插到隊尾便可。git

缺點:判斷一個頁面置換算法優劣的指標就是缺頁率,而FIFO算法的一個顯著的缺點是,在某些特定的時刻,缺頁率反而會隨着分配頁面的增長而增長,這稱爲Belady現象。產生Belady現象現象的緣由是,FIFO置換算法與進程訪問內存的動態特徵是不相容的,被置換的內存頁面每每是被頻繁訪問的,或者沒有給進程分配足夠的頁面,所以FIFO算法會使一些頁面頻繁地被替換和從新申請內存,從而致使缺頁率增長。所以,如今再也不使用FIFO算法github

LRU算法

LRU(The Least Recently Used,最近最久未使用算法)是一種常見的緩存算法,在不少分佈式緩存系統(如Redis, Memcached)中都有普遍使用。算法

LRU算法的思想是:若是一個數據在最近一段時間沒有被訪問到,那麼能夠認爲在未來它被訪問的可能性也很小。所以,當空間滿時,最久沒有訪問的數據最早被置換(淘汰)數組

LRU算法的描述: 設計一種緩存結構,該結構在構造時肯定大小,假設大小爲 K,並有兩個功能:緩存

  1. set(key,value):將記錄(key,value)插入該結構。當緩存滿時,將最久未使用的數據置換掉。
  2. get(key):返回key對應的value值。

實現:最樸素的思想就是用數組+時間戳的方式,不過這樣作效率較低。所以,咱們能夠用雙向鏈表(LinkedList)+哈希表(HashMap)實現(鏈表用來表示位置,哈希表用來存儲和查找),在Java裏有對應的數據結構LinkedHashMap數據結構

LInkedHashMap

利用JavaLinkedHashMap用很是簡單的代碼來實現基於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,並有兩個功能:

  1. set(key,value):將記錄(key,value)插入該結構。當緩存滿時,將訪問頻率最低的數據置換掉。
  2. get(key):返回key對應的value值。

算法實現策略:考慮到 LFU 會淘汰訪問頻率最小的數據,咱們須要一種合適的方法按大小順序維護數據訪問的頻率。LFU 算法本質上能夠看作是一個 top K 問題(K = 1),即選出頻率最小的元素,所以咱們很容易想到能夠用二項堆來選擇頻率最小的元素,這樣的實現比較高效。最終實現策略爲小頂堆+哈希表。

參考:

緩存算法(頁面置換算法)總結

談談緩存和基本的緩存算法

基於LinkedHashMap的LRUCache實現

Java集合詳解5:深刻理解LinkedHashMap和LRU緩存

相關文章
相關標籤/搜索