0

0

c++中如何实现LRU缓存淘汰算法_c++ LRU Cache设计思路

裘德小鎮的故事

裘德小鎮的故事

发布时间:2026-01-08 09:11:09

|

267人浏览过

|

来源于php中文网

原创

用 std::list + std::unordered_map 可实现 O(1) LRU 缓存:unordered_map 提供 key→list 迭代器映射以支持快速查找,list 支持头插、尾删和指定位置删除,共同满足 get/put/淘汰的常数时间要求。

c++中如何实现lru缓存淘汰算法_c++ lru cache设计思路

std::list + std::unordered_map 实现 O(1) LRU 缓存

LRU 缓存的核心难点是:既要快速查找(get),又要快速移动到最近使用位置(put更新顺序),还要在容量满时删掉最久未用项。C++ 标准库里,std::list 支持常数时间的节点删除和头/尾插入,std::unordered_map 提供 O(1) 的 key 查找 —— 二者组合刚好满足需求。

关键设计点:std::unordered_mapkey → iterator(指向 std::list 中对应 pair 的迭代器),这样 get 时能直接定位并移到链表头部;put 时若已存在,就用迭代器擦除原节点再重新插入头部;若超容,则删掉链表尾部节点,并从 map 中移除对应 key。

注意:std::list::erase(iterator) 是合法且高效的,但不能保存失效迭代器 —— 每次 erase 后原迭代器立即无效,所以必须先用 map[key] 获取、操作、再更新 map。

std::list 节点值类型选 std::pair 还是自定义结构体?

std::pair 最简洁,但可读性差;用结构体(如 struct CacheNode { int key; int val; };)更清晰,尤其当未来要扩展字段(比如访问时间戳、引用计数)时。实际性能无差异 —— std::list 存的是对象副本,两者都是 trivial 类型,移动开销一致。

立即学习C++免费学习笔记(深入)”;

实操建议:

  • 初期用 std::pair 快速验证逻辑
  • 上线前或需维护时,换成命名明确的结构体,避免 it->first/it->second 带来的歧义
  • 不要用 std::vectorstd::deque 替代 std::list:前者不支持 O(1) 中间删除,后者不保证迭代器在插入/删除后稳定

如何处理 put 时 key 已存在但 value 不同的情况?

LRU 规范要求:put(k, v) 若 k 已存在,应更新 value 并将该节点移到最近使用位置(即链表头),而不是忽略或报错。常见错误是只更新 map 中的 value,却忘了调整 list 顺序 —— 这会导致缓存淘汰逻辑错乱,最久未用项可能不是真实最久的。

Live PPT
Live PPT

一款AI智能化生成演示内容的在线工具。只需输入一句话、粘贴一段内容、或者导入文件,AI生成高质量PPT。

下载

正确做法(以 std::pair 为例):

if (cache_map.find(key) != cache_map.end()) {
    auto it = cache_map[key];
    cache_list.erase(it);  // 先删旧位置
}
cache_list.push_front({key, value});
cache_map[key] = cache_list.begin();  // 更新 map 中的迭代器

注意:push_front 返回新节点的迭代器,必须立刻存入 map;如果先 erase 再 push_front 但没更新 map,后续 get 会解引用已失效迭代器,触发未定义行为。

为什么不用 std::map 替代 std::unordered_map

std::map 是红黑树,查找/插入/删除都是 O(log n),而 LRU 要求所有核心操作 O(1),用它会让整体复杂度退化为 O(log n),尤其在高并发或大数据量场景下明显拖慢。除非你明确需要 key 有序遍历(LRU 本身不需要),否则没有理由选 std::map

另一个坑:std::unordered_map 的迭代器在 rehash 时会失效(例如插入导致扩容)。但只要你不保存跨操作的迭代器(即每次 get/put 都现场查 map 取迭代器),就完全安全 —— 因为 cache_map[key] 总返回当前有效迭代器。

补充:C++20 起可考虑 std::unordered_map::extract 避免拷贝,但对 int 这种小类型意义不大;真正受益的是带大成员的对象缓存。

最易被忽略的一点:std::list::iterator 不能序列化或跨线程共享,如果你的缓存要用于多线程环境,必须加锁(如 std::shared_mutex),且锁粒度要覆盖“查 map → 操作 list → 更新 map”整个临界区,不能只锁其中一步。

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

194

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

186

2025.07.04

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

315

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

529

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

50

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

193

2025.08.29

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

476

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

142

2025.12.24

java学习网站推荐汇总
java学习网站推荐汇总

本专题整合了java学习网站相关内容,阅读专题下面的文章了解更多详细内容。

33

2026.01.08

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
HTML5/CSS3/JavaScript/ES6入门课程
HTML5/CSS3/JavaScript/ES6入门课程

共102课时 | 6.6万人学习

前端基础到实战(HTML5+CSS3+ES6+NPM)
前端基础到实战(HTML5+CSS3+ES6+NPM)

共162课时 | 18.6万人学习

第二十二期_前端开发
第二十二期_前端开发

共119课时 | 12.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号