关键实现要点
-
数据结构选择
- 双向链表:维护访问顺序
- 哈希表:快速查找节点
-
淘汰策略
- 容量满时删除最久未使用的节点
- 访问时自动检查并删除超时节点
-
时间管理
- 使用
steady_clock记录节点时间戳 - 访问时更新节点时间
- 使用
完整代码实现
cpp
#include <unordered_map>
#include <list>
#include <chrono>
using namespace std;
using namespace std::chrono;
class TimedLRUCache {
private:
// 节点结构:存储键、值和访问时间
struct Node {
int key;
int value;
steady_clock::time_point timestamp;
Node(int k, int v) : key(k), value(v), timestamp(steady_clock::now()) {}
};
int capacity_; // 缓存容量
int timeout_ms_; // 超时时间(毫秒)
list<Node> cache_list_; // 双向链表,维护访问顺序
unordered_map<int, decltype(cache_list_.begin())> key_map_; // 哈希表,快速查找
public:
// 构造函数:设置容量和超时时间
TimedLRUCache(int capacity, int timeout_ms = 0)
: capacity_(capacity), timeout_ms_(timeout_ms) {}
// 获取值,不存在返回-1
int get(int key) {
auto it = key_map_.find(key);
if (it == key_map_.end()) return -1;
// 检查是否超时
if (isExpired(it->second)) {
removeNode(it->second);
return -1;
}
// 更新访问时间并移动到链表头部
it->second->timestamp = steady_clock::now();
cache_list_.splice(cache_list_.begin(), cache_list_, it->second);
return it->second->value;
}
// 插入或更新值
void put(int key, int value) {
auto it = key_map_.find(key);
if (it != key_map_.end()) {
// 键已存在,更新值和访问时间
it->second->value = value;
it->second->timestamp = steady_clock::now();
cache_list_.splice(cache_list_.begin(), cache_list_, it->second);
return;
}
// 检查容量,删除最久未使用的节点
if (cache_list_.size() >= capacity_) {
removeLRUNode();
}
// 插入新节点到链表头部
cache_list_.emplace_front(key, value);
key_map_[key] = cache_list_.begin();
}
// 检查键是否存在且未超时
bool contains(int key) {
auto it = key_map_.find(key);
if (it == key_map_.end()) return false;
if (isExpired(it->second)) {
removeNode(it->second);
return false;
}
return true;
}
// 获取当前缓存大小
int size() const {
return cache_list_.size();
}
// 获取缓存容量
int capacity() const {
return capacity_;
}
private:
// 检查节点是否超时
bool isExpired(decltype(cache_list_.begin()) iter) {
if (timeout_ms_ == 0) return false; // 0表示永不过期
auto now = steady_clock::now();
auto elapsed = duration_cast<milliseconds>(now - iter->timestamp);
return elapsed.count() > timeout_ms_;
}
// 删除指定节点
void removeNode(decltype(cache_list_.begin()) iter) {
key_map_.erase(iter->key);
cache_list_.erase(iter);
}
// 删除最久未使用的节点
void removeLRUNode() {
if (cache_list_.empty()) return;
// 从尾部开始查找未超时的节点删除
for (auto it = cache_list_.rbegin(); it != cache_list_.rend(); ++it) {
auto list_it = prev(it.base()); // 反向迭代器转正向迭代器
if (!isExpired(list_it)) {
removeNode(list_it);
return;
}
}
// 如果所有节点都超时,删除最后一个
if (!cache_list_.empty()) {
removeNode(prev(cache_list_.end()));
}
}
};
使用示例
cpp
#include <iostream>
#include <thread>
int main() {
// 创建缓存:容量3,超时时间1000ms
TimedLRUCache cache(3, 1000);
// 测试基本功能
cache.put(1, 100);
cache.put(2, 200);
cache.put(3, 300);
cout << "初始状态 - 大小: " << cache.size() << endl; // 3
cout << "获取键2: " << cache.get(2) << endl; // 200
// 测试超时功能
this_thread::sleep_for(milliseconds(1500));
cout << "超时后获取键1: " << cache.get(1) << endl; // -1 (已超时)
cout << "超时后大小: " << cache.size() << endl; // 2 (自动清理)
// 测试LRU淘汰
cache.put(4, 400);
cache.put(5, 500);
cout << "插入新键后大小: " << cache.size() << endl; // 3
return 0;
}
关键点梳理
1. 数据结构设计
- 双向链表:维护节点的访问顺序,最近访问的在头部
- 哈希表:提供O(1)时间复杂度的节点查找
- 节点结构:包含键、值和时间戳三要素
2. 淘汰策略实现
cpp
// 关键代码:删除最久未使用节点
void removeLRUNode() {
// 从尾部开始查找(最久未使用)
for (auto it = cache_list_.rbegin(); it != cache_list_.rend(); ++it) {
auto list_it = prev(it.base());
if (!isExpired(list_it)) { // 找到第一个未超时的节点
removeNode(list_it);
return;
}
}
// 所有节点都超时,删除最后一个
if (!cache_list_.empty()) {
removeNode(prev(cache_list_.end()));
}
}
3. 时间管理机制
cpp
// 关键代码:超时检查
bool isExpired(decltype(cache_list_.begin()) iter) {
if (timeout_ms_ == 0) return false; // 永不过期
auto now = steady_clock::now();
auto elapsed = duration_cast<milliseconds>(now - iter->timestamp);
return elapsed.count() > timeout_ms_;
}
4. 访问顺序维护
cpp
// 关键代码:节点访问时的处理
it->second->timestamp = steady_clock::now(); // 更新时间
cache_list_.splice(cache_list_.begin(), cache_list_, it->second); // 移动到头部
核心优势
- 时间复杂度:get和put操作都是O(1)
- 空间复杂度:O(capacity),只存储有限节点
- 自动清理:访问时自动检查并删除超时节点
- 线程安全:单线程环境下安全使用
这个实现平衡了功能完整性和代码简洁性,适合需要基础缓存功能的场景。