LRU-2与标准LRU的核心区别在于淘汰依据:标准LRU仅基于最近一次访问时间,而LRU-2要求至少两次访问,淘汰时优先选择第二次访问时间最久远的项,从而有效过滤单次偶发访问,缓解缓存污染。

LRU-2和标准LRU的核心区别在哪
LRU-2不是简单地记录最近一次访问,而是跟踪每个缓存项的**最近两次访问时间**;只有当某项被访问过至少两次,且第二次访问距今最久时,它才可能被淘汰。这能有效缓解“偶发热点”导致误淘汰的问题——比如某个key只被扫了一次就再没用过,LRU会把它留着,而LRU-2会更快踢掉它。
实现上,你需要为每个key维护两个时间戳:last_access 和 prev_access,每次访问都做「右移」:把当前last_access挪到prev_access,再更新last_access为当前时间。淘汰时,优先找prev_access最小(即第二次访问最久远)的项;若某key从未被访问两次,则prev_access为0或负无穷,这类项默认更早被淘汰。
用std::unordered_map + std::list组合是否可行
不行。标准LRU常用std::list维护访问顺序,靠splice把命中项移到表头——但LRU-2需要按**第二次访问时间**排序,而这个值在每次访问后都会变,无法靠链表位置静态维持。硬用std::list会导致淘汰逻辑退化成O(n)扫描,且插入/更新后需重新排序,破坏缓存性能。
更实际的做法是:
- 用
std::unordered_map存key → value和两个时间戳(封装成结构体) - 用
std::set或std::priority_queue维护可淘汰候选集,排序依据是prev_access - 注意:
std::set要求键可比较且稳定,所以不能直接存指针或引用;建议存key本身,查表时再取时间戳 - 每次访问
key后,要从std::set中删旧记录、插新记录(因为prev_access变了)
std::priority_queue实现淘汰队列要注意什么
std::priority_queue不支持修改内部元素,也没法高效删除指定key。如果仅用它存待淘汰候选,会在更新prev_access时产生 stale entry(陈旧条目),导致淘汰错误。
稳妥做法是用懒删除+std::set:
- 定义比较函数:按
prev_access升序,prev_access相同时按key升序避免重复 - 每次访问
key前,先在std::set里删掉该key的旧记录(用erase配合find) - 更新完时间戳后,把新记录
{key, prev_access}插入std::set - 淘汰时取
*cache_set.begin(),再验证其prev_access是否与map中一致(防竞争或延迟更新)
示例关键片段:
struct Candidate {
std::string key;
int64_t prev_access;
bool operator candidates_;
std::unordered_map<:string cacheentry> cache_;
</:string>
时间戳用什么类型、怎么获取才靠谱
别用time(nullptr)——秒级精度太粗,同一秒内多次访问会导致prev_access == last_access,逻辑错乱。也别用std::chrono::system_clock::now()转秒数,同样有精度问题。
推荐:
- 用
std::chrono::steady_clock::now().time_since_epoch().count(),返回纳秒级整数(实际是系统相关,但足够区分毫秒内多次访问) - 存为
int64_t,避免浮点误差和转换开销 - 如果跨线程使用,所有时间戳读写必须加锁,或用
std::atomic(但std::atomic不支持std::set排序,所以通常还是锁整个缓存操作)
真正容易被忽略的是:LRU-2对时钟单调性敏感。如果系统时间被NTP校正回拨,prev_access可能大于last_access,必须在更新时做校验并截断——例如强制令prev_access = std::min(prev_access, last_access - 1)。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











