std::list + std::unordered_map 是最实用的 LRU 实现组合,因二者协同实现 O(1) 查找、O(1) 移动到头部及 O(1) 尾部淘汰,避免 vector 或 map 的 O(n) 性能陷阱。
为什么 std::list + std::unordered_map 是最实用的 LRU 实现组合
因为既要 O(1) 查找,又要 O(1) 移动到头部(最近使用),还要支持快速淘汰尾部。用
或纯
都会掉进 O(n) 插入/删除陷阱——尤其在模拟大量页面访问时,性能会断崖式下跌。
实操建议:
立即学习
“
C++免费学习笔记(深入)
”;
存页号,保证插入/删除/移动都是常数时间;头是最新访问,尾是最久未用
做哈希索引,避免遍历 list 查页是否存在
每次
:先查 map → 存在则用 iterator 在 list 中
到开头;不存在则插入新页并更新 map 和 list,超容时删
手写链表容易踩的三个内存坑
不是不能写,而是新手一上手就崩在指针操作上:野指针、重复 delete、迭代器失效。尤其模拟中频繁增删,
后没置空、
后还用原 iterator,都会导致段错误或数据错乱。
实操建议:
立即学习
“
C++免费学习笔记(深入)
”;
绝对不要裸
/
;用
管理节点,自动释放
删节点前确认
和
指针非空,且双向更新(比如删中间节点时,前后节点的指针都要重连)
用
代替手写链表——它内部已处理好所有边界,
和
安全可靠
模拟页面访问时,怎么避免“命中但没更新 LRU 顺序”的逻辑漏洞
这是最隐蔽也最常出错的地方:查到页在缓存里,却只返回 true,忘记把对应节点移到 list 头部。结果缓存里页号没变,但 LRU 顺序停滞,后续淘汰完全失真。
C知道
CSDN推出的一款AI技术问答工具
下载
实操建议:
立即学习
“
C++免费学习笔记(深入)
”;
统一入口函数如
,内部必须包含“查 + 移动(若命中)+ 插入(若未命中)+ 淘汰(若超容)”四步,缺一不可
用
而非
,拿到 iterator 后直接用于
,避免二次查找
加一句 assert:
在 debug 模式下验证刚访问的页确实在头部
LRU 模拟和真实 OS 页面置换的区别在哪
模拟可以只管页号,真实 OS 还要管物理帧分配、修改位(dirty bit)、换入换出 I/O 开销——但这些不是 LRU 算法本身的责任。强行在模拟里加磁盘延迟或 dirty 标记,反而模糊了核心逻辑。
实操建议:
立即学习
“
C++免费学习笔记(深入)
”;
专注实现
/
接口行为:输入页号,输出是否命中、是否触发置换、淘汰了哪个页
测试用例优先覆盖边界:容量为 0、1、满容后连续访问新页、反复访问同一页、访问不存在页
别碰
或
——那是系统编程范畴;C++ 模拟只做算法验证,不是驱动开发
真正难的不是结构,是访问序列生成和命中率统计的准确性。一个错位的
放错位置,整组实验数据就废了。
std::vectorstd::mapstd::liststd::unordered_map::iterator> access_page()splice()back()deleteerase()newdeletestd::unique_ptrnextprevstd::listsplice()erase()visit(int page_id)std::unordered_map::find()count()splice()assert(cache_list.front() == page_id);get()put()mmapmincorepage_fault_count++