【redis前傳】自己手寫一個LRU策略 | redis淘汰策略

煙花散盡13141發表於2021-06-25

一、題目描述

146. LRU 快取機制

運用你所掌握的資料結構,設計和實現一個 LRU (最近最少使用) 快取機制 。
實現 LRUCache 類:

LRUCache(int capacity) 以正整數作為容量 capacity 初始化LRU快取
int get(int key) 如果關鍵字 key 存在於快取中,則返回關鍵字的值,否則返回 -1 。
void put(int key, int value) 如果關鍵字已經存在,則變更其資料值;如果關鍵字不存在,則插入該組「關鍵字-值」。當快取容量達到上限時,它應該在寫入新資料之前刪除最久未使用的資料值,從而為新的資料值留出空間。

進階:你是否可以在 O(1) 時間複雜度內完成這兩種操作?

image-20210611091820720

二、思路分析

第一想法

  • 剛看到本題時沒有多想就覺得會用到佇列,因為佇列FIFO可以做到淘汰末尾資料,但是仔細一想本題是需要淘汰最近最少使用資料,如果僅僅是最近的資料那麼佇列很容易實現。加上使用頻率就涉及到資料的頻繁挪動。很明顯佇列是無法完成的。
  • 那麼有沒有一種順序新增的資料,每次在獲取之後就會將資料前移至一端呢?答案是有的!LinkedHashMap
  • LinkedHashMap 不熟悉的朋友們可以簡單的將它理解成HashMap 。 下圖展示了HashMap的儲存結構

image-20210611091747182

  • 上述的元素我這裡做了個動畫演示全過程!!!

動畫演示

  • LinkedHashMap只是多了一條連結串列串起裡面的元素

  • 這也是為什麼LinkedHashMap是按照順序儲存的。但是LinkedHahsMap也無法做到按照使用頻率進行排序啊?大家都知道他是按照新增順序排序的!!!

LinkedHashMap改造

  • 原生的LinkedHashMap的確無法滿足情況,但是我們稍微看下原始碼能夠發現在put之後都會執行下afterNodeInsertion 這個方法。這也是HashMap留給LinkedHashMap做的擴充套件!

image-20210611095540549

image-20210611100631032

  • removeNode 就是將最前面的資料。想要進入這個方法就需要removeEldestEntry判斷。LinkedHashMap預設是false . 所以我們只需要重寫他就行了。但是還是在get值的時候如何保值在最後面呢?我們仔細看下原始碼就能夠發現在get中有這個一個方法afterNodeAccess 。他的作用就是將get的元素移位值後面。正好符合我們LRU的策略特徵

image-20210611101435521

  • 綜上!我們藉助LinkedHashMap就非常容易的實現了LRU策略!

image-20210611101819720

自己實現

  • 但是本題的意思是想考察我們自己是如何實現的,而不是巧妙對現有的工具改造的!不過上面對LinkedHashMap的確改造的很巧這是不可否認的!下面我們就嘗試自己來實現下這種方式!

  • 首先我們需要確定需要用到Hash結合連結串列來實現。Hash我們自然使用HashMap來儲存資料為的就是方便定位資料。定位到資料就需要操作連結串列將資料實時移位值連結串列尾部,每次淘汰是將連結串列首位移除既可。為了方便我們操作連結串列這裡的連結串列肯定是雙連結串列的!

連結串列單元

image-20210611104201991

  • 首先我們定義一個內部類!用於連結串列的基本單元。裡面儲存了key,value方便根據Hash中儲存的內容找到節點!preNodenextNode分別指向前後節點

image-20210611105808351

  • 在構建器中初始化容量和連結串列大小,並初始化邊界節點方便我們操作節點中移位和刪除。

image-20210611105924364

  • 在獲取資料時沒有新增就返回-1 , 已經新增的資料則將該資料對應的node節點移動到連結串列的尾部。

image-20210611110148633

  • 在put中當第一次新增我們需要維護連結串列大小並進行檢測是否需要進行淘汰資料,如果不是第一次新增我們只需奧更新值和對應node在連結串列中的位置即可

image-20210611110255277

方法名 作用
addToTail 將節點新增值連結串列尾部
moveToTail 將已經存在於連結串列中的節點移動到連結串列的尾部
removeHeadNode 刪除連結串列中第一個節點,注意是邊界節點後第一個節點

image-20210611110451514

四、總結

  • 雖然執行時間和記憶體消耗有點高!但是我就是不優化。
  • 本題主要就是在連結串列的移動上面會複雜點。我們需要按照新增順序和使用頻率兩個維度進行維護他們之間的順序。只要這個順序維護好,就沒啥問題了!

相關文章