【redis前傳】本身手寫一個LRU策略 | 抓住時間的尾巴

這是我參與更文挑戰的第23天,活動詳情查看: 更文挑戰node

1、題目描述

146. LRU 緩存機制

運用你所掌握的數據結構,設計和實現一個 LRU (最近最少使用) 緩存機制 。 實現 LRUCache 類:緩存

LRUCache(int capacity) 以正整數做爲容量 capacity 初始化LRU緩存 int get(int key) 若是關鍵字 key 存在於緩存中,則返回關鍵字的值,不然返回 -1 。 void put(int key, int value) 若是關鍵字已經存在,則變動其數據值;若是關鍵字不存在,則插入該組「關鍵字-值」。當緩存容量達到上限時,它應該在寫入新數據以前刪除最久未使用的數據值,從而爲新的數據值留出空間。markdown

進階:你是否能夠在 O(1) 時間複雜度內完成這兩種操做?數據結構

image-20210611091820720

2、思路分析

第一想法

  • 剛看到本題時沒有多想就以爲會用到隊列,由於隊列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來存儲數據爲的就是方便定位數據。定位到數據就須要操做鏈表將數據實時移位值鏈表尾部,每次淘汰是將鏈表首位移除既可。爲了方便咱們操做鏈表這裏的鏈表確定是雙鏈表的!oop

鏈表單元

image-20210611104201991

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

image-20210611105808351

  • 在構建器中初始化容量和鏈表大小,並初始化邊界節點方便咱們操做節點中移位和刪除。

image-20210611105924364

  • 在獲取數據時沒有添加就返回-1 , 已經添加的數據則將該數據對應的node節點移動到鏈表的尾部。

image-20210611110148633

  • 在put中當第一次添加咱們須要維護鏈表大小並進行檢測是否須要進行淘汰數據,若是不是第一次添加咱們只需奧更新值和對應node在鏈表中的位置便可

image-20210611110255277

方法名 做用
addToTail 將節點添加值鏈表尾部
moveToTail 將已經存在於鏈表中的節點移動到鏈表的尾部
removeHeadNode 刪除鏈表中第一個節點,注意是邊界節點後第一個節點

image-20210611110451514

4、總結

  • 雖然執行時間和內存消耗有點高!可是我就是不優化。
  • 本題主要就是在鏈表的移動上面會複雜點。咱們須要按照添加順序和使用頻率兩個維度進行維護他們之間的順序。只要這個順序維護好,就沒啥問題了!
相關文章
相關標籤/搜索