---
# System prepended metadata

title: LRU快取 (LRU Cache)

---

# 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)
```