緩存淘汰算法--LRU算法

1. LRU

  • 1.1. 原理
    • LRU(Least recently used誓禁,最近最少使用)算法根據(jù)數(shù)據(jù)的歷史訪問記錄來進行淘汰數(shù)據(jù),其核心思想是“如果數(shù)據(jù)最近被訪問過,那么將來被訪問的幾率也更高”。
  • 1.2. 實現(xiàn)
    最常見的實現(xiàn)是使用一個鏈表保存緩存數(shù)據(jù)如输,詳細(xì)算法實現(xiàn)如下:
    1. 新數(shù)據(jù)插入到鏈表頭部;
    1. 每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪問)央勒,則將數(shù)據(jù)移到鏈表頭部不见;
    1. 當(dāng)鏈表滿的時候,將鏈表尾部的數(shù)據(jù)丟棄崔步。
  • 1.3. 分析

    • 【命中率】
      • 當(dāng)存在熱點數(shù)據(jù)時脖祈,LRU的效率很好,但偶發(fā)性的刷晋、周期性的批量操作會導(dǎo)致LRU命中率急劇下降盖高,緩存污染情況比較嚴(yán)重。
    • 【復(fù)雜度】
      • 實現(xiàn)簡單眼虱。
    • 【代價】
      • 命中時需要遍歷鏈表喻奥,找到命中的數(shù)據(jù)塊索引,然后需要將數(shù)據(jù)移到頭部捏悬。

2. LRU-K

  • 2.1. 原理
    • LRU-K中的K代表最近使用的次數(shù)撞蚕,因此LRU可以認(rèn)為是LRU-1。LRU-K的主要目的是為了解決LRU算法“緩存污染”的問題过牙,其核心思想是將“最近使用過1次”的判斷標(biāo)準(zhǔn)擴展為“最近使用過K次”甥厦。
  • 2.2. 實現(xiàn)
    • 相比LRU,LRU-K需要多維護一個隊列寇钉,用于記錄所有緩存數(shù)據(jù)被訪問的歷史刀疙。只有當(dāng)數(shù)據(jù)的訪問次數(shù)達到K次的時候,才將數(shù)據(jù)放入緩存扫倡。當(dāng)需要淘汰數(shù)據(jù)時谦秧,LRU-K會淘汰第K次訪問時間距當(dāng)前時間最大的數(shù)據(jù)。詳細(xì)實現(xiàn)如下:
    1. 數(shù)據(jù)第一次被訪問撵溃,加入到訪問歷史列表疚鲤;
    1. 如果數(shù)據(jù)在訪問歷史列表里后沒有達到K次訪問,則按照一定規(guī)則(FIFO缘挑,LRU)淘汰集歇;
    1. 當(dāng)訪問歷史隊列中的數(shù)據(jù)訪問次數(shù)達到K次后,將數(shù)據(jù)索引從歷史隊列刪除语淘,將數(shù)據(jù)移到緩存隊列中诲宇,并緩存此數(shù)據(jù)际歼,緩存隊列重新按照時間排序;
    1. 緩存數(shù)據(jù)隊列中被再次訪問后焕窝,重新排序;
    1. 需要淘汰數(shù)據(jù)時维贺,淘汰緩存隊列中排在末尾的數(shù)據(jù)它掂,即:淘汰“倒數(shù)第K次訪問離現(xiàn)在最久”的數(shù)據(jù)。
      LRU-K具有LRU的優(yōu)點溯泣,同時能夠避免LRU的缺點虐秋,實際應(yīng)用中LRU-- 2是綜合各種因素后最優(yōu)的選擇,LRU-3或者更大的K值命中率會高垃沦,但適應(yīng)性差客给,需要大量的數(shù)據(jù)訪問才能將歷史訪問記錄清除掉。
  • 2.3. 分析

    • 【命中率】
      • LRU-K降低了“緩存污染”帶來的問題肢簿,命中率比LRU要高靶剑。
    • 【復(fù)雜度】
      • LRU-K隊列是一個優(yōu)先級隊列,算法復(fù)雜度和代價比較高池充。
    • 【代價】
      • 由于LRU-K還需要記錄那些被訪問過桩引、但還沒有放入緩存的對象,因此內(nèi)存消耗會比LRU要多收夸;當(dāng)數(shù)據(jù)量很大的時候坑匠,內(nèi)存消耗會比較可觀。
      • LRU-K需要基于時間進行排序(可以需要淘汰時再排序卧惜,也可以即時排序)厘灼,CPU消耗比LRU要高。

3. Two queues(2Q)

  • 3.1. 原理
    • Two queues(以下使用2Q代替)算法類似于LRU-2咽瓷,不同點在于2Q將LRU-2算法中的訪問歷史隊列(注意這不是緩存數(shù)據(jù)的)改為一個FIFO緩存隊列设凹,即:2Q算法有兩個緩存隊列,一個是FIFO隊列茅姜,一個是LRU隊列围来。
  • 3.2. 實現(xiàn)
    • 當(dāng)數(shù)據(jù)第一次訪問時,2Q算法將數(shù)據(jù)緩存在FIFO隊列里面匈睁,當(dāng)數(shù)據(jù)第二次被訪問時监透,則將數(shù)據(jù)從FIFO隊列移到LRU隊列里面,兩個隊列各自按照自己的方法淘汰數(shù)據(jù)航唆。詳細(xì)實現(xiàn)如下:
    1. 新訪問的數(shù)據(jù)插入到FIFO隊列胀蛮;
    1. 如果數(shù)據(jù)在FIFO隊列中一直沒有被再次訪問,則最終按照FIFO規(guī)則淘汰糯钙;
    1. 如果數(shù)據(jù)在FIFO隊列中被再次訪問粪狼,則將數(shù)據(jù)移到LRU隊列頭部退腥;
    1. 如果數(shù)據(jù)在LRU隊列再次被訪問,則將數(shù)據(jù)移到LRU隊列頭部再榄;
    1. LRU隊列淘汰末尾的數(shù)據(jù)狡刘。
  • 3.3. 分析

    • 【命中率】
      • 2Q算法的命中率要高于LRU。
    • 【復(fù)雜度】
      • 需要兩個隊列困鸥,但兩個隊列本身都比較簡單嗅蔬。
    • 【代價】
      • FIFO和LRU的代價之和。
  • 2Q算法和LRU-2算法命中率類似疾就,內(nèi)存消耗也比較接近澜术,但對于最后緩存的數(shù)據(jù)來說,2Q會減少一次從原始存儲讀取數(shù)據(jù)或者計算數(shù)據(jù)的操作猬腰。

4. Multi Queue(MQ)

  • 4.1. 原理
    • MQ算法根據(jù)訪問頻率將數(shù)據(jù)劃分為多個隊列鸟废,不同的隊列具有不同的訪問優(yōu)先級,其核心思想是:優(yōu)先緩存訪問次數(shù)多的數(shù)據(jù)姑荷。
  • 4.2. 實現(xiàn)
    • MQ算法將緩存劃分為多個LRU隊列盒延,每個隊列對應(yīng)不同的訪問優(yōu)先級。訪問優(yōu)先級是根據(jù)訪問次數(shù)計算出來的鼠冕,例如
      詳細(xì)的算法結(jié)構(gòu)圖如下兰英,Q0,Q1....Qk代表不同的優(yōu)先級隊列供鸠,Q-history代表從緩存中淘汰數(shù)據(jù)畦贸,但記錄了數(shù)據(jù)的索引和引用次數(shù)的隊列:

如上圖,算法詳細(xì)描述如下:

    1. 新插入的數(shù)據(jù)放入Q0楞捂;
    1. 每個隊列按照LRU管理數(shù)據(jù)薄坏;
    1. 當(dāng)數(shù)據(jù)的訪問次數(shù)達到一定次數(shù),需要提升優(yōu)先級時寨闹,將數(shù)據(jù)從當(dāng)前隊列刪除胶坠,加入到高一級隊列的頭部;
    1. 為了防止高優(yōu)先級數(shù)據(jù)永遠(yuǎn)不被淘汰繁堡,當(dāng)數(shù)據(jù)在指定的時間里訪問沒有被訪問時沈善,需要降低優(yōu)先級,將數(shù)據(jù)從當(dāng)前隊列刪除椭蹄,加入到低一級的隊列頭部闻牡;
    1. 需要淘汰數(shù)據(jù)時,從最低一級隊列開始按照LRU淘汰绳矩;每個隊列淘汰數(shù)據(jù)時罩润,將數(shù)據(jù)從緩存中刪除,將數(shù)據(jù)索引加入Q-history頭部翼馆;
    1. 如果數(shù)據(jù)在Q-history中被重新訪問割以,則重新計算其優(yōu)先級金度,移到目標(biāo)隊列的頭部;
    1. Q-history按照LRU淘汰數(shù)據(jù)的索引严沥。
  • 4.3. 分析

    • 【命中率】
      • MQ降低了“緩存污染”帶來的問題猜极,命中率比LRU要高。
    • 【復(fù)雜度】
      • MQ需要維護多個隊列消玄,且需要維護每個數(shù)據(jù)的訪問時間跟伏,復(fù)雜度比LRU高。
    • 【代價】
      • MQ需要記錄每個數(shù)據(jù)的訪問時間莱找,需要定時掃描所有隊列酬姆,代價比LRU要高嗜桌。
  • 注:雖然MQ的隊列看起來數(shù)量比較多奥溺,但由于所有隊列之和受限于緩存容量的大小,因此這里多個隊列長度之和和一個LRU隊列是一樣的骨宠,因此隊列掃描性能也相近浮定。

5. LRU類算法對比

由于不同的訪問模型導(dǎo)致命中率變化較大,此處對比僅基于理論定性分析层亿,不做定量分析桦卒。

  • 實際應(yīng)用中需要根據(jù)業(yè)務(wù)的需求和對數(shù)據(jù)的訪問情況進行選擇,并不是命中率越高越好匿又。例如:雖然LRU看起來命中率會低一些方灾,且存在”緩存污染“的問題,但由于其簡單和代價小碌更,實際應(yīng)用中反而應(yīng)用更多裕偿。

  • java中最簡單的LRU算法實現(xiàn),就是利用jdk的LinkedHashMap痛单,覆寫其中的removeEldestEntry(Map.Entry)方法即可.如果你去看LinkedHashMap的源碼可知嘿棘,LRU算法是通過雙向鏈表來實現(xiàn),當(dāng)某個位置被命中旭绒,通過調(diào)整鏈表的指向?qū)⒃撐恢谜{(diào)整到頭位置鸟妙,新加入的內(nèi)容直接放在鏈表頭,如此一來挥吵,最近被命中的內(nèi)容就向鏈表頭移動重父,需要替換時,鏈表最后的位置就是最近最少使用的位置忽匈。

基于雙鏈表 的LRU實現(xiàn):

  • 傳統(tǒng)意義的LRU算法是為每一個Cache對象設(shè)置一個計數(shù)器坪郭,每次Cache命中則給計數(shù)器+1,而Cache用完脉幢,需要淘汰舊內(nèi)容歪沃,放置新內(nèi)容時嗦锐,就查看所有的計數(shù)器,并將最少使用的內(nèi)容替換掉沪曙。

  • 它的弊端很明顯奕污,如果Cache的數(shù)量少,問題不會很大液走, 但是如果Cache的空間過大碳默,達到10W或者100W以上,一旦需要淘汰缘眶,則需要遍歷所有計算器嘱根,其性能與資源消耗是巨大的。效率也就非常的慢了巷懈。

  • 它的原理: 將Cache的所有位置都用雙連表連接起來该抒,當(dāng)一個位置被命中之后,就將通過調(diào)整鏈表的指向顶燕,將該位置調(diào)整到鏈表頭的位置凑保,新加入的Cache直接加到鏈表頭中。

  • 這樣涌攻,在多次進行Cache操作后欧引,最近被命中的,就會被向鏈表頭方向移動恳谎,而沒有命中的芝此,而想鏈表后面移動,鏈表尾則表示最近最少使用的Cache因痛。

  • 當(dāng)需要替換內(nèi)容時候婚苹,鏈表的最后位置就是最少被命中的位置,我們只需要淘汰鏈表最后的部分即可婚肆。

    • 上面說了這么多的理論租副, 下面用代碼來實現(xiàn)一個LRU策略的緩存。

    • 我們用一個對象來表示Cache较性,并實現(xiàn)雙鏈表

public class LRUCache {
    /**
     * 鏈表節(jié)點
     * @author Administrator
     *
     */
    class CacheNode {
        CacheNode prev;//前一節(jié)點
        CacheNode next;//后一節(jié)點
        Object value;//值
        Object key;//鍵
        CacheNode() {
        }
    }
    public LRUCache(int i) {
        currentSize = 0;
        cacheSize = i;
        nodes = new Hashtable(i);//緩存容器
    }
    
    /**
     * 獲取緩存中對象
     * @param key
     * @return
     */
    public Object get(Object key) {
        CacheNode node = (CacheNode) nodes.get(key);
        if (node != null) {
            moveToHead(node);
            return node.value;
        } else {
            return null;
        }
    }
    
    /**
     * 添加緩存
     * @param key
     * @param value
     */
    public void put(Object key, Object value) {
        CacheNode node = (CacheNode) nodes.get(key);
        
        if (node == null) {
            //緩存容器是否已經(jīng)超過大小.
            if (currentSize >= cacheSize) {
                if (last != null)//將最少使用的刪除
                    nodes.remove(last.key);
                removeLast();
            } else {
                currentSize++;
            }
            
            node = new CacheNode();
        }
        node.value = value;
        node.key = key;
        //將最新使用的節(jié)點放到鏈表頭用僧,表示最新使用的.
        moveToHead(node);
        nodes.put(key, node);
    }
    /**
     * 將緩存刪除
     * @param key
     * @return
     */
    public Object remove(Object key) {
        CacheNode node = (CacheNode) nodes.get(key);
        if (node != null) {
            if (node.prev != null) {
                node.prev.next = node.next;
            }
            if (node.next != null) {
                node.next.prev = node.prev;
            }
            if (last == node)
                last = node.prev;
            if (first == node)
                first = node.next;
        }
        return node;
    }
    public void clear() {
        first = null;
        last = null;
    }
    /**
     * 刪除鏈表尾部節(jié)點
     *  表示 刪除最少使用的緩存對象
     */
    private void removeLast() {
        //鏈表尾不為空,則將鏈表尾指向null. 刪除連表尾(刪除最少使用的緩存對象)
        if (last != null) {
            if (last.prev != null)
                last.prev.next = null;
            else
                first = null;
            last = last.prev;
        }
    }
    
    /**
     * 移動到鏈表頭,表示這個節(jié)點是最新使用過的
     * @param node
     */
    private void moveToHead(CacheNode node) {
        if (node == first)
            return;
        if (node.prev != null)
            node.prev.next = node.next;
        if (node.next != null)
            node.next.prev = node.prev;
        if (last == node)
            last = node.prev;
        if (first != null) {
            node.next = first;
            first.prev = node;
        }
        first = node;
        node.prev = null;
        if (last == null)
            last = first;
    }
    private int cacheSize;
    private Hashtable nodes;//緩存容器
    private int currentSize;
    private CacheNode first;//鏈表頭
    private CacheNode last;//鏈表尾
}

本文轉(zhuǎn)載自 緩存淘汰算法--LRU算法赞咙。

241天以來责循,Java架構(gòu)更新了 602個主題,已經(jīng)有100+位同學(xué)加入攀操。微信掃碼關(guān)注java架構(gòu)院仿,獲取Java面試題和架構(gòu)師相關(guān)題目和視頻。上述相關(guān)面試題答案,盡在Java架構(gòu)中歹垫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末剥汤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子排惨,更是在濱河造成了極大的恐慌吭敢,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暮芭,死亡現(xiàn)場離奇詭異鹿驼,居然都是意外死亡,警方通過查閱死者的電腦和手機辕宏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門畜晰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瑞筐,你說我怎么就攤上這事凄鼻。” “怎么了面哼?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵野宜,是天一觀的道長扫步。 經(jīng)常有香客問我魔策,道長,這世上最難降的妖魔是什么河胎? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任闯袒,我火速辦了婚禮,結(jié)果婚禮上游岳,老公的妹妹穿的比我還像新娘政敢。我一直安慰自己,他們只是感情好胚迫,可當(dāng)我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布喷户。 她就那樣靜靜地躺著,像睡著了一般访锻。 火紅的嫁衣襯著肌膚如雪褪尝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天期犬,我揣著相機與錄音河哑,去河邊找鬼。 笑死龟虎,一個胖子當(dāng)著我的面吹牛璃谨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼佳吞,長吁一口氣:“原來是場噩夢啊……” “哼拱雏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起底扳,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤古涧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后花盐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體羡滑,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年算芯,在試婚紗的時候發(fā)現(xiàn)自己被綠了柒昏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡熙揍,死狀恐怖职祷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情届囚,我是刑警寧澤有梆,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站意系,受9級特大地震影響泥耀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蛔添,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一痰催、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧迎瞧,春花似錦夸溶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至足绅,卻和暖如春捷绑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背编检。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工胎食, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人允懂。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓厕怜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子粥航,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 1. LRU 1.1.原理 LRU(Leastrecentlyused琅捏,最近最少使用)算法根據(jù)數(shù)據(jù)的歷史訪問記錄來...
    安易學(xué)車閱讀 2,545評論 0 23
  • 緩存淘汰算法--LRU算法 1. LRU 1.1 原理 LRU(Least recently used,)算法根據(jù)...
    白公子是貓奴閱讀 479評論 0 0
  • LRU原理 LRU(Least recently used递雀,最近最少使用)算法根據(jù)數(shù)據(jù)的歷史訪問記錄來進行淘汰數(shù)據(jù)...
    jiangmo閱讀 60,245評論 3 30
  • Android緩存淺析 By吳思博 1柄延、引言 2、常見的幾種緩存算法 3缀程、Android緩存的機制 4搜吧、LruCa...
    吳小博Toby閱讀 2,916評論 1 5
  • 心情起伏不定,早上出門見客戶杨凑,開著車聽著老歌滤奈,腦袋閃過一些回憶,內(nèi)心是充盈的撩满,試圖把這種溫暖的感覺占據(jù)我的每一個細(xì)...
    甜心教主閱讀 87評論 0 0