# LRU快取 (LRU Cache)
## 快取機制
LRU Cache 機制藉由資料取用的頻率和時間來做為快取的順序
需要實現的方法有兩個:
1. put(key,value)
把資料放入快取,放入的時候,會把資料的順序放在第一個
2. get()
取出資料,並把資料的順序更改到第一個
## 實現
一般的 LRU Cache 使用 Double Linked List + Hash Map 來實現
Double Linked List 裡面主要會儲存 key + value,會使用 Dobule Linked List 是因為要排序 LRU Cache 的順序
Dobule Linked List 會有一個 head 和一個 tail,用來定位最前面和最後面的節點,還有方便插入節點
Hash Map 的 value 是用來指向 node,眾所周知,Linked List 在查詢資料的時候需要花上 O(n) 的時間,所以使用 Hash Map 就可以把查詢快取的時候所短到 O(1),結構如下:
**{ key: node (pointer) }**
LRU Cache 都會有一個數字 capacity 來限制快取大小,當快取的容量大於這個數字時,LRU Cache 就會移除掉最後一筆資料,來確保 Cache 不會溢出
```python=!
class DListNode: # 雙向鏈表節點
def __init__(self, key: int = -1, val: int = -1, prev = None, next = None):
self.key = key
self.val = val
self.prev = prev
self.prev = prev
class DLinkedList: # 雙向鏈表實現
def __init__(self):
self.head = DListNode()
self.tail = DListNode()
self.head.next = self.tail
self.tail.prev = self.head
def insert(self, head: DListNode, node: DListNode, tail: DListNode) -> None:
head.next = node
node.prev = head
node.next = tail
tail.prev = node
def insertToFirst(self, node: DListNode) -> None: # 插入節點到第一個位置
head = self.head
tail = self.head.next
self.insert(head,node,tail)
def remove(self, node: DListNode) -> None: # 移除節點
print(node.key,node.val,node.prev,node.next)
head = node.prev
tail = node.next
head.next = tail
tail.prev = head
node.prev = None
node.next = None
def changeOrderToFirst(self, node: DListNode) -> None: # 把節點的順序插入到第一個
self.remove(node)
self.insertToFirst(node)
class LRUCache(DLinkedList):
def __init__(self, capacity: int):
super().__init__()
self.capacity = capacity
self.current_capacity = 0
self.hash_map = {}
@property
def reach_to_maximin_capacity(self) -> bool: # 查看是否超出 capacity
return self.current_capacity > self.capacity
def get(self, key: int) -> int:
if key in self.hash_map: # 利用 Hash Map O(1) 的特性 查看是否在快取裡
node = self.hash_map[key]
self.changeOrderToFirst(node) # 把資料移到第一個位置
return node.val
return -1
def put(self, key: int, value: int) -> None:
if key not in self.hash_map:
node = DListNode(key,value) # 如果沒有就創建一個資料
self.insertToFirst(node) # 插入到第一個
self.hash_map[key] = node # 把節點指針方入 hash map
self.current_capacity += 1 # 增加 capacity
if self.reach_to_maximin_capacity: # 如果超出就移除,這裡有一個說法,就是其實在寫入資料之前,應該要先看有沒有溢出,如果有就先刪除在插入,才是比較好的做法
node = self.hash_map[self.tail.prev.key] # 找到前面的節點
del self.hash_map[self.tail.prev.key] # 刪除 hash map 裡的指針
self.remove(node) # 移出雙向鏈表裡的節點
self.current_capacity -= 1 # 修改 capacity
else: # 如果有就把原本的節點找出來修改資料,並把順序移動到第一個
node = self.hash_map[key]
node.val = value
self.changeOrderToFirst(node)
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
```