用std::list+std::unordered_map实现lru缓存:list维护访问顺序,map以key映射list迭代器,get/put通过splice o(1)移动节点,超容时删list尾并同步清除map项,避免迭代器失效;不用vector/deque因非o(1)移动,不用map因o(log n)查找拖慢性能。

用 std::list + std::unordered_map 实现最简 LRU
LRU 缓存的核心是「快速查+快速挪到最近使用位置+超容时删最久未用」,C++ 标准库里没有现成的 LRU 容器,但组合 std::list(维护访问顺序)和 std::unordered_map(O(1) 查 key)就能稳稳搞定。
关键点在于:map 存的是 key → list<pair value>>::iterator</pair>,这样每次 get 时能直接用迭代器把对应节点 splice 到 list 头部,避免查找开销。
- 别用
std::vector或std::deque替代std::list—— 删除中间元素或移动节点不是 O(1) -
std::list::splice是唯一安全且高效把已有节点移到头部的方法;别用erase+push_front,会触发析构/构造,对非 trivial 类型有风险 - map 的 value 类型必须是迭代器,不能存索引或指针——
list迭代器在插入/删除时只对被操作节点失效,其他有效
get 和 put 中迭代器失效怎么防
最容易翻车的地方:在 get 里拿到迭代器后,还没来得及 splice,就先做了 map 的修改(比如误调了 erase),导致迭代器悬空;或者在 put 超容时删尾部节点,却忘了同步从 map 里 erase 对应 key。
-
get流程必须是:查 map → 取迭代器 →splice到头部 → 返回值。中间不能穿插任何可能使该迭代器失效的操作 -
put里若 key 已存在,要先用原迭代器splice到头,再更新 value;不要先erase再insert,否则旧迭代器立刻失效 - 删最久未用项(
list.back())前,务必先用它的 key 去 map 里erase,否则残留脏指针
为什么不用 std::map 替代 std::unordered_map
因为 LRU 本身不依赖 key 排序,而 std::map 是红黑树,O(log n) 查找,直接拖慢所有 get/put;std::unordered_map 平均 O(1),更匹配 LRU 的性能预期。
立即学习“C++免费学习笔记(深入)”;
- 除非你的 key 类型不支持哈希(比如自定义结构没写
std::hash特化),否则别退化到map - 注意
unordered_map的 rehash 可能使所有迭代器失效——但它只影响 map 自身迭代器,不影响你存的list::iterator,所以安全 - 如果担心哈希冲突导致退化,可预设 bucket 数:
reserve(capacity),避免运行中频繁 rehash
线程安全要自己加锁,标准容器不负责
std::list 和 std::unordered_map 都不是线程安全的,多个线程同时 get/put 会大概率崩在迭代器或内部指针上,不是数据错,是直接 crash。
- 读多写少场景,可用
std::shared_mutex(C++17),get用 shared_lock,put用 unique_lock - 别只锁 map 或只锁 list——两个容器操作必须原子:比如
get既要读 map 又要改 list,必须同一把锁覆盖 - 锁粒度别太细:有人想分别锁 map 和 list 来提升并发,但 LRU 的语义要求二者强一致,拆锁反而引入竞态
边界情况比想象中多:比如 put 时刚好触发容量淘汰,要删 list 尾 + 删 map key,这两步必须在同一个临界区里做完。漏掉任意一环,缓存就会逻辑错乱,而且很难复现。










