前言

快取是一種提高資料讀取效能的技術,在計算機中cpu和主記憶體之間讀取資料存在差異,CPU和主記憶體之間有CPU快取,而且在記憶體和硬碟有記憶體快取。當主存容量遠大於CPU快取,或磁碟容量遠大於主存時,哪些資料應該被應該被清理,哪些資料應該被保留,這就需要快取淘汰策略來決定。常見的策略有三種:先進先出策略FIFO(First In,First Out)、最少使用策略LFU(Least Frequently Used)、最近最少使用策略LRU(Least Recently Used)。

LRU描述

設計和實現一個  LRU (最近最少使用) 快取機制 。

實現 LRUCache 類:

  • LRUCache(int capacity) 以正整數作為容量 capacity 初始化 LRU 快取
  • int get(int key) 如果關鍵字 key 存在於快取中,則返回關鍵字的值,否則返回 -1 。
  • void put(int key, int value) 如果關鍵字已經存在,則變更其資料值;如果關鍵字不存在,則插入該組「關鍵字-值」。當快取容量達到上限時,它應該在寫入新資料之前刪除最久未使用的資料值,從而為新的資料值留出空間。

解題思路 雜湊表 + 雙向連結串列

  • 針對LRU的特點,選擇使用雙鏈表實現。
  • 使用 gut 方法獲取資料,如果有資料,把返回資料,並且把資料放在連結串列頭部。
  • 使用 put 方法存放資料,如果資料存在,直接覆蓋新值;如果資料不存在,新增新值。新值都放在連結串列頭部。此外,還需要判斷快取有沒有超出容量 capacity,如果有超出,刪除連結串列的尾結點。
  • 因為是單鏈表,每次獲取資料,或者刪除資料,都需要遍歷一遍連結串列,時間複雜度是O(n),這裡使用hash來記錄每個資料的位置,將資料訪問的時間複雜度降到O(1)。
class LRUCache {

    class DLinkedNode{
int key;
int value;
DLinkedNode prev;
DLinkedNode next; public DLinkedNode() {} public DLinkedNode(int key, int value) {
this.key = key;
this.value = value;
}
} private int size; private int capacity; private DLinkedNode head; private DLinkedNode tail; private Map<Integer,DLinkedNode> cache = new HashMap<>(); public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
} public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
//找到並移動到首位
moveToHead(node);
return node.value; } public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
//不存在就建立一個新的節點
DLinkedNode newNode = new DLinkedNode(key,value);
cache.put(key,newNode);
addToHead(newNode);
size++;
if (size > capacity) {
//超出容量,移除最後節點
DLinkedNode tail = removeTail();
cache.remove(tail.key);
size--;
}
} else {
//key存在,覆蓋value,並移到頭部
if (node.value != value) {
node.value = value;
}
moveToHead(node); }
} private DLinkedNode removeTail() {
DLinkedNode node = tail.prev;
removeNode(node);
return node;
} private DLinkedNode removeNode(DLinkedNode node) {
node.next.prev = node.prev;
node.prev.next = node.next;
return node;
} private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
} private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
}

參考

LRU維基百科

極客時間-王爭-如何實現LRU快取淘汰演算法?