LRU算法的Java實(shí)現(xiàn)

LRU算法介紹

LRU算法全稱Least Recently Used长赞,也就是檢查最近最少使用的數(shù)據(jù)的算法渠啤。這個算法通常使用在內(nèi)存淘汰策略中跛锌,用于將不常用的數(shù)據(jù)轉(zhuǎn)移出內(nèi)存贱呐,將空間騰給最近更常用的“熱點(diǎn)數(shù)據(jù)”丧诺。
初識這個算法忘了是在操作系統(tǒng)課還是計(jì)算機(jī)組成原理課上,其在Redis奄薇、Guava等工具中也有非常廣泛的應(yīng)用驳阎,甚至是最核心的思想之一。如果今后需要自己設(shè)計(jì)系統(tǒng)馁蒂,即使不自己實(shí)現(xiàn)這個算法呵晚,LRU的思想也仍然是很重要的。

算法很簡單远搪,只需要將所有數(shù)據(jù)按使用時間排序劣纲,在需要篩選出LRU數(shù)據(jù)時,取排名靠后的即可谁鳍。

算法實(shí)現(xiàn)

Redis中的LRU

Redis中的數(shù)據(jù)量通常很龐大,如果每次對全量數(shù)據(jù)進(jìn)行排序劫瞳,勢必將對服務(wù)吞吐量造成影響倘潜。因此,Redis在LRU淘汰部分key時志于,使用的是采樣并計(jì)算近似LRU的涮因,因此淘汰的是局部LRU數(shù)據(jù)。
Redis內(nèi)存淘汰策略
maxmemory-policy配置可選參數(shù):

  • noeviction:不淘汰伺绽,內(nèi)存超限后寫命令會返回錯誤(如OOM, del命令除外)
  • allkeys-lru:所有key的LRU機(jī)制 在所有key中按照最近最少使用LRU原則剔除key养泡,釋放空間
  • volatile-lru:易失key的LRU 僅以設(shè)置過期時間key范圍內(nèi)的LRU(如均為設(shè)置過期時間,則不會淘汰)
  • allkeys-random:所有key隨機(jī)淘汰 一視同仁奈应,隨機(jī)
  • volatile-random:易失Key的隨機(jī) 僅設(shè)置過期時間key范圍內(nèi)的隨機(jī)
  • volatile-ttl:易失key的TTL淘汰 按最小TTL的key優(yōu)先淘汰

Redis LRU的效果

左上-理論LRU效果澜掩;右上-Redis3.0中的近似LRU(采樣值10);左下-Redis2.8中的近似LRU(采樣值5)杖挣;右下-Redis3.0中的近似LRU(采樣值5)
淺灰色-被淘汰肩榕;灰色-未被淘汰;綠色-新寫入

補(bǔ)充說明:

  • 達(dá)到設(shè)定的內(nèi)存占用閾值時才會進(jìn)行內(nèi)存淘汰
  • maxmemory-samples配置表示采樣值惩妇,每次刪除時采集的樣本數(shù)——采樣值10株汉,表示從設(shè)置中定義的key中取10個key進(jìn)行LRU計(jì)算并刪除LRU的那個key
  • Redis3.0中的算法建立了一個“候選池”,使得算法的效率和準(zhǔn)確率都比2.8有提高歌殃,因?yàn)榉秶s小了

結(jié)論:

  • Redis3.0通過增加候選池提高了LRU準(zhǔn)確性乔妈,效果比2.8好
  • 采樣值越高越結(jié)果越接近理論LRU(但是采樣值越高效率低)
  • 差不多采樣率5就已經(jīng)足夠準(zhǔn)確了,當(dāng)然使用10已經(jīng)基本接近理論LRU結(jié)果氓皱,但是損失效率

Java中的LRU實(shí)現(xiàn)思路

根據(jù)LRU算法路召,在Java中實(shí)現(xiàn)需要這些條件:

  • 底層數(shù)據(jù)使用雙向鏈表贮懈,方便在鏈表的任意位置進(jìn)行刪除,在鏈表尾進(jìn)行添加
    這一點(diǎn)用單鏈表比較費(fèi)勁优训,當(dāng)然用數(shù)組等結(jié)構(gòu)也都很費(fèi)勁
    當(dāng)然雙向鏈表在查找時也麻煩朵你,但下述可以結(jié)合HashMap使用
  • 需要將鏈表按照訪問(使用)順序排序
  • 數(shù)據(jù)量超過一定閾值后,需要刪除Least Recently Used數(shù)據(jù)

Java中一個簡單的LRUCache實(shí)現(xiàn)

對于上述的實(shí)現(xiàn)思路揣非,java.util.LinkedHashMap已經(jīng)實(shí)現(xiàn)了其中的99%抡医,因此直接基于LinkedHashMap實(shí)現(xiàn)LRUCache非常簡單。

LinkedHashMap為LRUCache鋪墊了什么

  • 構(gòu)造方法提供了accessOrder選項(xiàng)早敬,開啟后會get方法會有額外操作保證鏈表順序按訪問順序逆序排列
  • 底層結(jié)構(gòu)使用雙向鏈表忌傻,查找可以使用HashMap的特點(diǎn)
  • 覆蓋了父類HashMap的newNode方法和newTreeNode方法,這兩個方法在HashMap中只是創(chuàng)建Node用的搞监,而在LinkedHashMap中不但創(chuàng)建Node水孩,還將Node放在鏈表末尾
  • 父類HashMap提供了3個void的Hook方法,方法沒做任何事:
    • afterNodeRemoval 父類在remove一個集合中存在的元素后調(diào)用
    • afterNodeInsertion 父類在put琐驴、compute俘种、merge后調(diào)用
    • afterNodeAccess 父類在replace、compute绝淡、merge等替換值后會調(diào)用宙刘,LinkedHashMap在get中開啟accessOrder時調(diào)用,究其根本是在對數(shù)據(jù)有操作時會調(diào)用
  • LinkedHashMap本質(zhì)上還是復(fù)用HashMap的絕大部分功能牢酵,包括底層的Node<K, V>[]悬包,因此能支持原本HashMap的功能
  • 但是LinkedHashMap實(shí)現(xiàn)了父類HashMap的3個Hook方法:
    • afterNodeRemoval 實(shí)現(xiàn)鏈表的刪除操作
    • afterNodeInsertion 并沒有實(shí)現(xiàn)鏈表的插入操作,但新添加了一個Hook方法boolean removeEldestEntry馍乙,當(dāng)這個Hook方法返回true時布近,刪除鏈表頭的節(jié)點(diǎn)
    • afterNodeAccess 如前所述,開啟accessOrder后會將被操作的節(jié)點(diǎn)放在鏈表末尾丝格,保證鏈表順序按訪問順序逆序排列
  • 上一條3個方法是用來構(gòu)建雙向鏈表的撑瞧,LinkedHashMap還覆蓋了父類的3個方法:
    • newNode 在創(chuàng)建一個Node的同時,將Node添加到鏈表末尾
    • newTreeNode 創(chuàng)建TreeNode的同時铁追,將Node添加到鏈表末尾
    • get 完成get功能的同時季蚂,如果accessOrder開啟,會調(diào)用afterNodeAccess將Node移動到鏈表末尾
      覆蓋newNodenewTreeNode方法后琅束,在put方法中調(diào)用的newNodenewTreeNode方法也就連帶實(shí)現(xiàn)了鏈表的插入操作

綜上扭屁,我們可以了解到LinkedHashMap為什么能夠輕松實(shí)現(xiàn)LRUCache

  1. 繼承父類HashMap,擁有HashMap的功能涩禀,因此在查找一個節(jié)點(diǎn)時時間復(fù)雜度為O(1)料滥,再加上鏈表是雙向,做鏈表任意節(jié)點(diǎn)的刪除工作就非常簡單
  2. 通過HashMap提供的3個Hook方法并覆蓋了2個創(chuàng)建Node的方法艾船,實(shí)現(xiàn)了自身鏈表的添加葵腹、刪除工作高每,保證在不影響原本Array功能的前提下,正確完成自身的鏈表構(gòu)建践宴;這個過程實(shí)際上均是通過Hook方式增強(qiáng)原有功能的鲸匿,因?yàn)樵镜腍ashMap中創(chuàng)建節(jié)點(diǎn)其實(shí)也是使用的Hook方法
  3. 提供屬性accessOrder并實(shí)現(xiàn)了afterNodeAccess方法,因此能夠根據(jù)訪問或操作順序?qū)⒆罱褂没蜃罱迦氲臄?shù)據(jù)放在鏈表尾阻肩,越久沒被使用的數(shù)據(jù)就越靠近鏈表頭带欢,實(shí)現(xiàn)了整個鏈表按照LRU的要求排序
  4. 提供了一個Hook方法boolean removeEldestEntry,這個方法返回true時將會刪除表頭節(jié)點(diǎn)烤惊,即LRU中應(yīng)當(dāng)淘汰的節(jié)點(diǎn)乔煞,但是這個方法在LinkedHashMap中的實(shí)現(xiàn)永遠(yuǎn)返回false

到這為止,實(shí)現(xiàn)一個LRUCache就很簡單了:實(shí)現(xiàn)這個removeEldestEntryHook方法柒室,給LinkedHashMap設(shè)置一個閾值渡贾,那么超過這個閾值時就會進(jìn)行LRU淘汰。

網(wǎng)上隨處可見的Java代碼實(shí)現(xiàn)

    // 繼承LinkedHashMap
    public class LRUCache<K, V> extends LinkedHashMap<K, V> {
        private final int MAX_CACHE_SIZE;

        public LRUCache(int cacheSize) {
            // 使用構(gòu)造方法 public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
            // initialCapacity雄右、loadFactor都不重要
            // accessOrder要設(shè)置為true空骚,按訪問排序
            super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
            MAX_CACHE_SIZE = cacheSize;
        }

        @Override
        protected boolean removeEldestEntry(Map.Entry eldest) {
            // 超過閾值時返回true,進(jìn)行LRU淘汰
            return size() > MAX_CACHE_SIZE;
        }

    }

看似幾行代碼解決的事兒不脯,其實(shí)只是冰山一角而已府怯。

參考資料

Using Redis as an LRU cache – Redis

本文搬自我的博客,歡迎參觀防楷!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市则涯,隨后出現(xiàn)的幾起案子复局,更是在濱河造成了極大的恐慌,老刑警劉巖粟判,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亿昏,死亡現(xiàn)場離奇詭異,居然都是意外死亡档礁,警方通過查閱死者的電腦和手機(jī)角钩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呻澜,“玉大人递礼,你說我怎么就攤上這事「遥” “怎么了脊髓?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長栅受。 經(jīng)常有香客問我将硝,道長恭朗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任依疼,我火速辦了婚禮痰腮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘律罢。我一直安慰自己膀值,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布弟翘。 她就那樣靜靜地躺著虫腋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稀余。 梳的紋絲不亂的頭發(fā)上悦冀,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機(jī)與錄音睛琳,去河邊找鬼盒蟆。 笑死,一個胖子當(dāng)著我的面吹牛师骗,可吹牛的內(nèi)容都是我干的历等。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼辟癌,長吁一口氣:“原來是場噩夢啊……” “哼寒屯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起黍少,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤寡夹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后厂置,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體菩掏,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年昵济,在試婚紗的時候發(fā)現(xiàn)自己被綠了智绸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡访忿,死狀恐怖瞧栗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情醉顽,我是刑警寧澤沼溜,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站游添,受9級特大地震影響系草,放射性物質(zhì)發(fā)生泄漏通熄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一找都、第九天 我趴在偏房一處隱蔽的房頂上張望唇辨。 院中可真熱鬧,春花似錦能耻、人聲如沸赏枚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饿幅。三九已至,卻和暖如春戒职,著一層夾襖步出監(jiān)牢的瞬間栗恩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工洪燥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留磕秤,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓捧韵,卻偏偏與公主長得像市咆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子再来,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

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