【C++】【常见面试题】最简版带大小和超时限制的LRU缓存实现
关键实现要点
- 
数据结构选择 - 双向链表:维护访问顺序
- 哈希表:快速查找节点
 
- 
淘汰策略 - 容量满时删除最久未使用的节点
- 访问时自动检查并删除超时节点
 
- 
时间管理 - 使用steady_clock记录节点时间戳
- 访问时更新节点时间
 
- 使用
完整代码实现
#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) {}// 获取值,不存在返回-1int 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()));}}
};
使用示例
#include <iostream>
#include <thread>int main() {// 创建缓存:容量3,超时时间1000msTimedLRUCache cache(3, 1000);// 测试基本功能cache.put(1, 100);cache.put(2, 200);cache.put(3, 300);cout << "初始状态 - 大小: " << cache.size() << endl; // 3cout << "获取键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;  // 3return 0;
}
关键点梳理
1. 数据结构设计
- 双向链表:维护节点的访问顺序,最近访问的在头部
- 哈希表:提供O(1)时间复杂度的节点查找
- 节点结构:包含键、值和时间戳三要素
2. 淘汰策略实现
// 关键代码:删除最久未使用节点
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. 时间管理机制
// 关键代码:超时检查
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. 访问顺序维护
// 关键代码:节点访问时的处理
it->second->timestamp = steady_clock::now();  // 更新时间
cache_list_.splice(cache_list_.begin(), cache_list_, it->second); // 移动到头部
核心优势
- 时间复杂度:get和put操作都是O(1)
- 空间复杂度:O(capacity),只存储有限节点
- 自动清理:访问时自动检查并删除超时节点
- 线程安全:单线程环境下安全使用
这个实现平衡了功能完整性和代码简洁性,适合需要基础缓存功能的场景。
