0

0

c++怎么实现lru缓存淘汰算法_c++ map与双向链表结合实现【方法】

尼克

尼克

发布时间:2026-01-07 14:33:07

|

360人浏览过

|

来源于php中文网

原创

应手写双向链表节点+裸指针映射,因std::list+std::unordered_map易致迭代器失效;需精确控制指针、统一初始化、严格按序操作内存,哨兵模式简化边界处理。

c++怎么实现lru缓存淘汰算法_c++ map与双向链表结合实现【方法】

为什么不用 std::list + std::unordered_map 就容易出错

直接用 std::list 存值、std::unordered_map 存键到迭代器的映射,看似简洁,但实际运行时极易触发迭代器失效:比如 std::list::spliceerase 后,若 map 中缓存的迭代器没同步更新,下一次访问就会 undefined behavior。更隐蔽的问题是,std::list::iterator 不能跨容器复用,也不能直接保存为裸指针——必须确保 map 中的迭代器始终指向当前 list 中有效节点。

  • 每次 get()put() 涉及节点移动时,必须先用 map.erase(key),再 list.push_front(),最后 map[key] = list.begin()
  • 不能用 list.splice(list.begin(), list, it) 把中间节点挪到头部——因为 it 来自 map 查得,而 splice 后原 it 在旧位置失效,但 map 里还留着它
  • std::listsize() 是 O(1),但频繁 erase + push_front 仍需注意内存局部性,小对象影响不大,大对象建议用 std::list<:unique_ptr>>

手写双向链表节点比依赖 STL 更可控

标准容器封装太深,LRU 核心逻辑(头插、尾删、任意节点提至头部)需要精确控制指针。自己定义节点结构,能避免迭代器生命周期管理问题,也方便调试和注入日志。

struct Node {
    int key;
    int value;
    Node* prev;
    Node* next;
    Node(int k, int v) : key(k), value(v), prev(nullptr), next(nullptr) {}
};
  • 构造时统一初始化 prev/nextnullptr,避免野指针
  • put() 中删除最久未用节点时,先从 maperase(old_tail->key),再 delete old_tail,顺序不能反
  • 提频操作(如 get() 命中后移到头部)只需四步:断开原链接 → 插入头部 → 更新 head → 修正新节点的 prev/next

std::unordered_map 是性能与安全的平衡点

用原始指针而非智能指针,是因为节点生命周期完全由 LRU 容器自身管理(头插、尾删、替换全在类内闭环),引入 std::shared_ptr 会增加原子计数开销,且无必要;而 std::unique_ptr 会导致 map 中存储的是移动后的独占权,无法支持「查找后提频」这类需多次访问同一节点的场景。

  • 插入新节点时:new Node → 插入 list 头部 → cache_map[key] = new_node
  • 删除节点时:先 cache_map.erase(node->key),再手动 delete node,否则内存泄漏
  • 不建议用 std::map 替代 std::unordered_map:LRU 不要求有序,红黑树的 O(log n) 查找会拖慢热点路径

边界处理比算法本身更容易翻车

LRU 类里最容易被忽略的是容量为 0 或 1 的情况,以及重复 put 相同 key 的行为——这既是更新值,也是访问频次提升,必须触发提频逻辑,而不是简单覆盖。

CrePal
CrePal

一站式AI视频创作Agent

下载

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

  • 构造时 capacity 应直接设为 0,并使所有操作退化为 no-op(get 返回 -1,put 不存)
  • put(key, value) 遇到已存在 key:必须先从链表中移除对应节点,再插入头部,同时更新值——不能只改 node->value,否则链表顺序不变,违背 LRU 语义
  • 尾节点(tail)的 prev 指针永远指向倒数第二个节点,tail 本身不存有效数据(哨兵模式),可省去大量空指针判断;但务必在构造函数中初始化 head->next = tail, tail->prev = head

手写节点+裸指针映射的组合,看着低级,实则把所有生命周期和链接逻辑暴露出来,反而不容易漏掉某处 prev 没置空、某次 erase 忘了更新 map 这类细节。真正难的不是“怎么连”,而是“什么时候连、连完谁负责清理”。

相关专题

更多
空指针异常处理
空指针异常处理

本专题整合了空指针异常解决方法,阅读专题下面的文章了解更多详细内容。

20

2025.11.16

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

73

2025.09.05

golang map相关教程
golang map相关教程

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

27

2025.11.16

golang map原理
golang map原理

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

57

2025.11.17

java判断map相关教程
java判断map相关教程

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

33

2025.11.27

数据库Delete用法
数据库Delete用法

数据库Delete用法:1、删除单条记录;2、删除多条记录;3、删除所有记录;4、删除特定条件的记录。更多关于数据库Delete的内容,大家可以访问下面的文章。

269

2023.11.13

drop和delete的区别
drop和delete的区别

drop和delete的区别:1、功能与用途;2、操作对象;3、可逆性;4、空间释放;5、执行速度与效率;6、与其他命令的交互;7、影响的持久性;8、语法和执行;9、触发器与约束;10、事务处理。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

208

2023.12.29

undefined是什么
undefined是什么

undefined是代表一个值或变量不存在或未定义的状态。它可以作为默认值来判断一个变量是否已经被赋值,也可以用于设置默认参数值。尽管在不同的编程语言中,undefined可能具有不同的含义和用法,但理解undefined的概念可以帮助我们更好地理解和编写程序。本专题为大家提供undefined相关的各种文章、以及下载和课程。

4171

2023.07.31

C++ 高性能计算与并行编程
C++ 高性能计算与并行编程

本专题专注于 C++ 在高性能计算(HPC)与并行编程中的应用,涵盖多线程、并发数据处理、OpenMP、MPI、GPU加速等技术。通过实际案例,帮助开发者掌握 如何利用 C++ 进行大规模数据计算和并行处理,提高程序的执行效率,适应高性能计算与数据密集型应用场景。

5

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号