什麼是 lru cache
lru cache 演算法是 least recently used,也就是最近最少使用演算法。
對於乙個作業系統來說,我們的快取是有限的,所以有的時候我們必須要捨棄掉一些 object 來增加當前程式的執行效率。lru cache 演算法的概念是:當快取空間滿了的時候,將最近最少使用的資料從快取空間中刪除以增加可用的快取空間來快取新的資料。這個演算法的核心是乙個快取列表,當我們在快取中的資料被訪問的時候,這個資料就會被提到列表的頭部,這樣的話列表尾部的資料就是不會經常被訪問的資料。當快取空間不足的時候,就會刪除列表尾部的資料來提公升程式執行效率。
lru cache 的核心是什麼?
lru cache 相當於要維護乙個跟時間順序相關的資料結構,那麼能找到最早更新元素的資料結構有 queue、heap 和 linkedlist 這幾種:
queue :佇列是先進先出的資料結構,我們確實知道哪乙個元素是先進去的,哪乙個元素是後進去的。
heap :我們可以通過維護乙個丟入時間 time 來確定哪乙個元素是先進去的,哪乙個元素是後進去的。
linkedlist :鍊錶有頭部和尾部,我們自然而然可以利用頭部來始終存放最新的元素(即最近使用的那個元素)
選擇哪一種?
我們知道一旦某個中間進去的元素突然被呼叫後,我們就應該將它的位置更新到頭部。頻繁更新位置對於 queue 來說時間消耗太多,因此排除;
有時候我們要刪除某乙個很久不用的元素。如果用 heap 的話我們可能需要遍歷所有的元素才能找到 time 為最早的那個節點,除非我們同時維護乙個最小堆。但是每次更新元素位置還是需要 o(lgn) 的時間來調整,遠不如 linkedlist 快;
linkedlist 對於插入刪除元素只需要 o(1) 的時間,但是我們還需要能夠快速地訪問到指定需要被更新的元素。這一點 linkedlist 要用 o(n) 遍歷,但是我們可以通過乙個字典來對應 key 和 node 的資訊,這樣就可以用 o(1) 的時間來找到對應元素了;
再進一步,由於要隨時刪除指定的 node,我們還需要將該 node 前的 node 與其後方的 node 相連,所以會有乙個找指定 node 前面乙個 node 的需求。雙向鍊錶 doubly linkedlist 就可以用 o(1) 時間來實現這個需求,所以我們確定使用雙向鍊錶 doubly linkedlist 來完成乙個lru cache。
確定了要使用的資料結構之後,我們來捋一捋接下來的思路:
1、lru cache 整體設計:
lru cache 裡面維護乙個 cache 字典對應 key 和 node 的資訊。乙個 cap 表示最大容量,乙個雙向鍊錶,其中 head.next 是 most recently (最近使用)的 node,tail.prev 是 least recently(最近最少使用) 的 node(即 lru 容量滿了會被刪除的那個 node)。
2、對於 get 方法:
如果 key 在 cache 字典中,說明 node 在鍊錶中
根據 key 從 cache 字典中拿到對應的 node,從雙向鍊錶中刪除這個 node,再向雙向鍊錶中重新插入這個 node(插入邏輯包含了更新到最新的位置)
如果不在直接返回 -1
3、對於 put 方法:
如果 key 在 cache 字典中,說明 node 在鍊錶中
根據 key 從 cache 字典中拿到對應的 node,從雙向鍊錶中刪除這個 node,再向雙向鍊錶中重新插入這個 node(插入邏輯包含了更新到最新的位置)
如果 key 不在 cache 字典中,說明是乙個新的 node
如果此時容量還沒滿的話:
生成新 node,插入雙向鍊錶中,同時放入 cache 中
如果此時容量滿了的話:
從雙向鍊錶中刪除 tail.prev,即 least recently 的 node
從 cache 中刪除這個 node 的資訊
生成新 node,插入雙向鍊錶中,放入 cache 中
其中 邏輯3 中如果 key 不在 cache 字典中的這一段**可以優化,
生成新 node,插入鍊錶中,放入 cache 中這一步是重複的。
其中 邏輯3 中如果 key 不在 cache 字典中的這一段**可以優化。
優化的原因:生成新 node,插入鍊錶中,放入 cache 中這一步是重複的。
優化的方法:定義乙個insert方法,當要執行這一步操作的時候直接呼叫即可。
class
node
(object):
def__init__
(self, key, val)
: self.key = key
self.val = val
self.
next
=none
self.prev =
none
class
lrucache
(object):
def__init__
(self, capacity)
:"""
:type capacity: int
"""self.cache =
self.cap = capacity
self.head = node(
none
,none
) self.tail = node(
none
,none
) self.head.
next
= self.tail
self.tail.prev = self.head
# 從雙向鍊錶中刪除這個 node
defremove
(self, node)
: n = node.
next
p = node.prev
p.next
= n n.prev = p
node.
next
=none
node.prev =
none
# 向雙向鍊錶中插入 node,並放到最新位置
definsert
(self, node)
: n = self.head.
next
self.head.
next
= node
node.
next
= n n.prev = node
node.prev = self.head
defget(self, key)
:"""
:type key: int
:rtype: int
"""# 如果 key 在 cache 字典中,說明 node在鍊錶中
if key in self.cache:
# 根據 key 從 cache 字典中拿到對應的 node
node = self.cache[key]
# 從雙向鍊錶中刪除這個node
self.remove(node)
# 再向雙向鍊錶中重新插入這個 node(插入邏輯包含了更新到最新的位置)
self.insert(node)
return node.val
else
:# 如果不在直接返回 -1
return-1
defput
(self, key, value)
:"""
:type key: int
:type value: int
:rtype: void
"""# 如果 key 在 cache 字典中,說明 node 在鍊錶中
if key in self.cache:
# 根據 key 從 cache 字典中拿到對應的 node
node = self.cache[key]
# 更新 node 的 val
node.val = value
# 從雙向鍊錶中刪除這個 `node`
self.remove(node)
# 再向雙向鍊錶中重新插入這個 node(插入邏輯包含了更新到最新的位置)
self.insert(node)
else
:# 如果 key 不在 cache 字典中,說明是乙個新的 node
# 如果此時容量滿了的話,我們需要先刪除 least recently 的 node
iflen
(self.cache)
== self.cap:
delete_node = self.tail.prev
del self.cache[delete_node.key]
self.remove(delete_node)
# 生成新 node
node = node(key, value)
# 插入雙向鍊錶中
self.insert(node)
# 同時放入 cache 中
self.cache[key]
= node
# your lrucache object will be instantiated and called as such:
# obj = lrucache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
LRU Cache 最近最少使用快取演算法
lru least recently used cache,直譯為最近最少使用快取演算法。因cache的資源容量是寶貴而有限的,當有新的內容需要新增到快取中時,就需要捨棄一部分原有的內容,lru的原則就是將最近最少使用的內容替換掉。典型的實現方法為 hash map 雙向鍊錶。雙向鍊錶 用來儲存帶有...
LruCache演算法(最近最少使用演算法)用法分析
其實說到記憶體快取的實現,非常容易就讓人想到lrucache演算法 least recently used 也叫近期最少使用演算法。它的主要演算法原理就是把最近使用的物件用強引用儲存在linkedhashmap中,並且把最近最少使用的物件在快取值達到預設定值之前從記憶體中移除。lrucache的用法...
自適應Lru(最近最少使用)演算法
在快取管理演算法中,lru 幾乎是公認的最優的演算法。然而它也有一些缺陷,主要是因為 它假定對實體的訪問有區域性特性。當訪問模式沒有區域性特性的時候,它就會退化為fifo 先進先出 演算法。在我寫乙個檔案系統的實現時,這種現象很讓我頭疼,因為很多時候,對乙個檔案的訪問大多是順序的,前面讀取過的內容幾...