後端開發中,資料庫查詢是效能瓶頸。LRU(Least Recently Used,最近最少使用)快取策略將熱點資料保留在記憶體中:
template <class K, class T>
struct Node {
K key;
T data;
Node *prev, *next;
};
template <class K, class T>
class LRUCache {
public:
LRUCache(size_t capacity);
~LRUCache();
void Put(K key, T data); // 插入/更新
T Get(K key); // 查詢
private:
void Attach(Node<K,T>* node); // 移至頭部(最近使用)
void Detach(Node<K,T>* node); // 從鏈表移除
hash_map<K, Node<K,T>*> hashmap; // O(1) 查找
vector<Node<K,T>*> freeList; // 記憶體池:預分配節點
Node<K,T> *head, *tail; // 虛擬頭尾節點
Node<K,T> *nodes; // 節點陣列
size_t capacity;
};
hashmap[key]:
hashmap[key]:
#include <iostream>
#include <vector>
#include <ext/hash_map>
using namespace std;
using namespace __gnu_cxx;
template <class K, class T>
struct Node {
K key;
T data;
Node *prev, *next;
Node() : prev(nullptr), next(nullptr) {}
};
template <class K, class T>
class LRUCache {
public:
LRUCache(size_t cap) : capacity(cap) {
nodes = new Node<K,T>[capacity];
freeList.reserve(capacity);
for (size_t i = 0; i < capacity; ++i)
freeList.push_back(&nodes[i]);
head = new Node<K,T>();
tail = new Node<K,T>();
head->next = tail;
tail->prev = head;
}
~LRUCache() {
delete[] nodes;
delete head;
delete tail;
}
void Put(K key, T data) {
auto it = hashmap.find(key);
Node<K,T> *node;
if (it != hashmap.end()) {
// 更新現有節點
node = it->second;
node->data = data;
Detach(node);
} else {
// 需要新節點
if (freeList.empty()) {
// 快取滿:淘汰 LRU(tail->prev)
node = tail->prev;
Detach(node);
hashmap.erase(node->key);
} else {
node = freeList.back();
freeList.pop_back();
}
node->key = key;
node->data = data;
hashmap[key] = node;
}
Attach(node);
}
T Get(K key) {
auto it = hashmap.find(key);
if (it == hashmap.end())
return T(); // 或拋出異常
Node<K,T> *node = it->second;
Detach(node);
Attach(node);
return node->data;
}
private:
void Attach(Node<K,T>* node) {
// 插入到 head 之後
node->next = head->next;
node->prev = head;
head->next->prev = node;
head->next = node;
}
void Detach(Node<K,T>* node) {
node->prev->next = node->next;
node->next->prev = node->prev;
}
hash_map<K, Node<K,T>*> hashmap;
vector<Node<K,T>*> freeList;
Node<K,T> *head, *tail;
Node<K,T> *nodes;
size_t capacity;
};
所有節點的 next 指向自己,鏈表結構崩壞。
// 錯誤寫法
for (size_t i = 0; i < capacity; ++i)
freeList.push_back(nodes + 1); // 寫成 1 而非 i!
// 正確寫法
for (size_t i = 0; i < capacity; ++i)
freeList.push_back(nodes + i);
導致 freeList 中全是同一個節點指標,反覆分配同一塊記憶體,Attach/Detach 操作相互覆蓋指標。
hash_map 非 C++ 標準,GCC 在 <ext/hash_map>,命名空間 __gnu_cxxunordered_mapLRU 快取結合 Hash Map O(1) 查找 與 雙向鏈表 O(1) 移動,是高效快取的經典實作。掌握記憶體池管理、虛擬頭尾節點技巧、指標操作正確性,是從「會用」到「會造」的關鍵。