LRU (最近最少使用演算法) 快取
阿新 • • 發佈:2021-02-09
技術標籤:演算法
目標
1. 該快取通過構造引數限制最大快取數量 capacity,當快取容量 size 達到上限時,它應該在寫入新資料之前刪除最久未使用的資料值,從而為新的資料值留出空間。
2. GET 以及 PUT 操作都滿足時間複雜度O(1)。
思路
為了知道最不經常使用快取資料是哪個,我們可以用連結串列來儲存,接近頭部的是最近剛使用的,接近尾部的是最不經常使用的。該快取還要刪除最近不經常使用的節點,需要使用雙向連結串列更方便。
GET 以及 PUT 都要滿足時間複雜度O(1),唯一可以想到的就是HashMap了。
在刪除尾部節點時,也要刪除 HashMap 裡的鍵值對,所以,節點中不僅要儲存value,也需要儲存 key。
為了操作方便,我們給雙向連結串列給定一個預設的head節點和一個tail節點。
HashMap在這裡主要角色是快取容器,雙向連結串列則是用來記錄快取資料是否最近使用過。
具體操作
對於 get 操作,先判斷 key 是否存在 HashMap中:
1. key 存在,通過 HashMap 獲取到對應的 node, 把 node 移動到第一個節點 (head.next),並返回該節點儲存的值。
2. key不存在,則返回null。
對於 put 操作,先判斷 key 是否存在 HashMap中:
1. 如果 key 存在,通過 HashMap 獲取到對應的 node,更新該節點的值。並且把node移動到第一個節點 (head.next)。
2. 如果key不存在:
- 如果 size < capacity,用 key 和 value 生成一個新的node,插入到雙向連結串列的頭部,並將容量加一。
- 如果 size >=capacity, 把尾部的節點(tail.pre)刪除掉。然後用 key 和 value生成一個新的 node,插入到雙向連結串列的頭部。
實現
import java.util.HashMap; import java.util.Map; public class LRUCache<K, V> { private int size; private int capacity; private Map<K, CacheNode> map = new HashMap<K, CacheNode>(); private CacheNode head = new CacheNode(); private CacheNode tail = new CacheNode(); class CacheNode { CacheNode pre; CacheNode next; K key; V value; public CacheNode() {} public CacheNode(K key, V value) { this.key = key; this.value = value; } } public LRUCache(int capacity) { this.capacity = capacity; this.size = 0; head.next = tail; tail.pre = head; } public V get(K key) { CacheNode node = map.get(key); if (node == null) { return null; } moveToHead(node); return node.value; } public void put(K key, V value) { CacheNode node = map.get(key); if (node == null) { CacheNode newNode = new CacheNode(key, value); addToHead(newNode); map.put(key, newNode); size++; if (size > capacity) { removeTail(); } } else { node.value = value; moveToHead(node); } } private void removeTail() { CacheNode lastNode = tail.pre; map.remove(lastNode.key); removeNode(lastNode); size--; } private void moveToHead(CacheNode node) { removeNode(node); addToHead(node); } private void removeNode(CacheNode node) { node.pre.next = node.next; node.next.pre = node.pre; } private void addToHead(CacheNode node) { node.next = head.next; head.next.pre = node; node.pre = head; head.next = node; } }
如果需要在多執行緒下使用快取,則還需要在 get 和 put 的方法上加上 synchronized 修飾符,保證執行緒安全。
public synchronized V get(K key) {
CacheNode node = map.get(key);
if (node == null) {
return null;
}
moveToHead(node);
return node.value;
}
public synchronized void put(K key, V value) {
CacheNode node = map.get(key);
if (node == null) {
CacheNode newNode = new CacheNode(key, value);
addToHead(newNode);
map.put(key, newNode);
size++;
if (size > capacity) {
removeTail();
}
} else {
node.value = value;
moveToHead(node);
}
}