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










