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

轻敏吖_2538

轻敏吖_2538

2026-01-08

287人浏览

原创

用 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_map 存 key → 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 类型,移动开销一致。

实操建议:

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

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

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

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

logos-node
logos-node

在 Linux x86_64 上安装、更新或检查 Logos 区块链测试网验证节点。用于用户需要搭建 Logos 节点时使用。

下载
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”整个临界区,不能只锁其中一步。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关文章

c++速学教程(入门到精通)
c++速学教程(入门到精通)

c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

node 大数据 c++ 标准库

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

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

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

2025.06.09

5494

18

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

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

2025.07.04

5671

25

string转int
string转int

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

2023.08.02

7599

3

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

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

2024.08.29

3605

6

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

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

2025.08.29

4748

10

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

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

2025.08.29

3165

10

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

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

2023.08.10

4918

6

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

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

2025.12.24

1369

20

java多线程相关教程合集
java多线程相关教程合集

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

2026.01.21

463

30

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WebSocket手册
WebSocket手册

共0课时 | 0人学习

HTML5/CSS3/JavaScript/ES6入门课程
HTML5/CSS3/JavaScript/ES6入门课程

共102课时 | 11.2万人学习

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

共162课时 | 29.2万人学习