title: 自己手寫一個LRU策略
date: 2021-06-18 12:00:30
tags:
- [redis]
- [lru]
categories:
- [redis]
permalink: zxh
prefix: redis
一、題目描述
146. LRU 緩存機制
運用你所掌握的數據結構,設計和實現一個
LRU
(最近最少使用) 緩存機制 。
實現LRUCache
類:
LRUCache(int capacity)
以正整數作爲容量 capacity 初始化LRU
緩存
int get(int key) 如果關鍵字 key 存在於緩存中,則返回關鍵字的值,否則返回 -1 。
void put(int key, int value) 如果關鍵字已經存在,則變更其數據值;如果關鍵字不存在,則插入該組「關鍵字-值」。當緩存容量達到上限時,它應該在寫入新數據之前刪除最久未使用的數據值,從而爲新的數據值留出空間。進階:你是否可以在 O(1) 時間複雜度內完成這兩種操作?
二、思路分析
第一想法
- 剛看到本題時沒有多想就覺得會用到隊列,因爲隊列FIFO可以做到淘汰末尾數據,但是仔細一想本題是需要淘汰最近最少使用數據,如果僅僅是最近的數據那麼隊列很容易實現。加上使用頻率就涉及到數據的頻繁挪動。很明顯隊列是無法完成的。
- 那麼有沒有一種順序添加的數據,每次在獲取之後就會將數據前移至一端呢?答案是有的!
LinkedHashMap
LinkedHashMap
不熟悉的朋友們可以簡單的將它理解成HashMap
。 下圖展示了HashMap
的存儲結構
- 上述的元素我這裏做了個動畫演示全過程!!!
- 而
LinkedHashMap
只是多了一條鏈表串起裏面的元素
- 這也是爲什麼
LinkedHashMap
是按照順序存儲的。但是LinkedHahsMap
也無法做到按照使用頻率進行排序啊?大家都知道他是按照添加順序排序的!!!
LinkedHashMap
改造
- 原生的
LinkedHashMap
的確無法滿足情況,但是我們稍微看下源碼能夠發現在put之後都會執行下afterNodeInsertion
這個方法。這也是HashMap
留給LinkedHashMap
做的擴展!
removeNode
就是將最前面的數據。想要進入這個方法就需要removeEldestEntry
判斷。LinkedHashMap
默認是false . 所以我們只需要重寫他就行了。但是還是在get值的時候如何保值在最後面呢?我們仔細看下源碼就能夠發現在get
中有這個一個方法afterNodeAccess
。他的作用就是將get的元素移位值後面。正好符合我們LRU
的策略特徵
- 綜上!我們藉助
LinkedHashMap
就非常容易的實現了LRU策略!
自己實現
-
但是本題的意思是想考察我們自己是如何實現的,而不是巧妙對現有的工具改造的!不過上面對
LinkedHashMap
的確改造的很巧這是不可否認的!下面我們就嘗試自己來實現下這種方式! -
首先我們需要確定需要用到Hash結合鏈表來實現。Hash我們自然使用
HashMap
來存儲數據爲的就是方便定位數據。定位到數據就需要操作鏈表將數據實時移位值鏈表尾部,每次淘汰是將鏈表首位移除既可。爲了方便我們操作鏈表這裏的鏈表肯定是雙鏈表的!
鏈表單元
- 首先我們定義一個內部類!用於鏈表的基本單元。裏面存儲了key,value方便根據Hash中存儲的內容找到節點!
preNode
,nextNode
分別指向前後節點
- 在構建器中初始化容量和鏈表大小,並初始化邊界節點方便我們操作節點中移位和刪除。
- 在獲取數據時沒有添加就返回-1 , 已經添加的數據則將該數據對應的node節點移動到鏈表的尾部。
- 在put中當第一次添加我們需要維護鏈表大小並進行檢測是否需要進行淘汰數據,如果不是第一次添加我們只需奧更新值和對應node在鏈表中的位置即可
方法名 | 作用 |
---|---|
addToTail | 將節點添加值鏈表尾部 |
moveToTail | 將已經存在於鏈表中的節點移動到鏈表的尾部 |
removeHeadNode | 刪除鏈表中第一個節點,注意是邊界節點後第一個節點 |
四、總結
- 雖然執行時間和內存消耗有點高!但是我就是不優化。
- 本題主要就是在鏈表的移動上面會複雜點。我們需要按照添加順序和使用頻率兩個維度進行維護他們之間的順序。只要這個順序維護好,就沒啥問題了!