news 2026/11/5 12:07:41

LRU 缓存原理与 C++ 实践示例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LRU 缓存原理与 C++ 实践示例

文章目录

  • LRU 缓存原理与 C++ 实践示例
    • 什么是 LRU
      • LRU 需要满足的操作
      • 数据结构选型
    • C++ 常见使用场景
      • 完整可运行示例
    • 实际项目中的注意点
    • 开源工业级实现参考
    • 小结

LRU 缓存原理与 C++ 实践示例

什么是 LRU

LRU(Least Recently Used),最近最少使用,是一种常见的缓存淘汰算法。

核心思想:如果一个数据最近被访问过,那么它在未来被访问的概率也更高;反之,很久没有被访问的数据,未来访问概率低,缓存满的时候优先把它淘汰掉。

缓存容量是有限的,当缓存达到上限,插入新元素时,需要剔除掉「最久没有使用」的那条数据,腾出空间存放新数据。

LRU 需要满足的操作

  1. get(key):读取 key。
    • key存在:返回 value,并且把该节点标记为最近使用;
    • key不存在:返回空。
  2. put(key, value):写入键值对。
    • key已存在:更新 value,标记为最近使用;
    • key不存在:插入新节点。如果缓存已满,删除最久未使用的节点,再插入新节点。

数据结构选型

要做到get、put平均 O(1) 时间复杂度:

  • 哈希表(unordered_map):实现 key 到节点的快速映射,O(1) 查找。
  • 双向链表:维护访问时序。链表头部代表最近使用,链表尾部代表最久未使用。
    • 访问到某个节点:把节点移动到链表头部;
    • 缓存淘汰:直接删除链表尾部节点。

单向链表无法 O(1) 删除中间节点,所以必须双向链表。


C++ 常见使用场景

  1. 服务端内存缓存:限制内存占用,缓存数据库查询结果,避免频繁查库。比如用户信息、配置信息缓存。
  2. 本地资源缓存:文件解析结果、图片解码结果缓存,防止内存暴涨。
  3. 中间件/网关:限流、会话缓存、请求结果缓存。
  4. 嵌入式程序:有限内存下缓存高频访问数据。

C++ 标准库没有直接提供 LRU 容器,一般两种方案:

  1. 自己实现unordered_map + list;
  2. 使用开源库(Abseilabsl::LRUCache,folly LRUCache)。

下面给出手写简易 LRUCache 实现,基于 C++17。

完整可运行示例

#include <iostream> #include <unordered_map> #include <list> #include <utility> /** * LRUCache 简易实现 * key: int, value: int */ class LRUCache { public: // list 存储 pair<key, value>,头部是最近使用,尾部最久未使用 using ListNode = std::pair<int, int>; std::list<ListNode> m_list; // key -> list 的迭代器 std::unordered_map<int, std::list<ListNode>::iterator> m_map; int m_capacity; explicit LRUCache(int capacity) : m_capacity(capacity) {} int get(int key) { auto it = m_map.find(key); if (it == m_map.end()) { return -1; // 不存在 } // 取出节点,移动到链表头部 auto node_it = it->second; int val = node_it->second; // splice:转移节点,不会拷贝,O(1) m_list.splice(m_list.begin(), m_list, node_it); return val; } void put(int key, int value) { auto it = m_map.find(key); if (it != m_map.end()) { // key已存在,更新value,挪到头部 auto node_it = it->second; node_it->second = value; m_list.splice(m_list.begin(), m_list, node_it); return; } // 容量满,淘汰尾部 if ((int)m_list.size() >= m_capacity) { auto last = m_list.back(); int del_key = last.first; m_map.erase(del_key); m_list.pop_back(); } // 插入新节点到头部 m_list.emplace_front(key, value); m_map[key] = m_list.begin(); } // 打印缓存内容,用于调试 void print() const { for (const auto& p : m_list) { std::cout << "[" << p.first << ":" << p.second << "] "; } std::cout << "\n"; } }; // 模拟业务场景:缓存数据库查询结果 int main() { // 缓存最多3条记录 LRUCache cache(3); std::cout << "put(1,100)\n"; cache.put(1, 100); cache.print(); std::cout << "put(2,200)\n"; cache.put(2, 200); cache.print(); std::cout << "put(3,300)\n"; cache.put(3, 300); cache.print(); std::cout << "get(1) -> " << cache.get(1) << "\n"; cache.print(); std::cout << "put(4,400) 缓存满,淘汰最久未使用的key=2\n"; cache.put(4, 400); cache.print(); std::cout << "get(2) -> " << cache.get(2) << "\n"; return 0; }

输出结果:

put(1,100) [1:100] put(2,200) [2:200] [1:100] put(3,300) [3:300] [2:200] [1:100] get(1) -> 100 [1:100] [3:300] [2:200] put(4,400) 缓存满,淘汰最久未使用的key=2 [4:400] [1:100] [3:300] get(2) -> -1

逻辑解释:访问 key=1 之后,1被挪到链表头部;插入4时容量已满,删除尾部key=2。

实际项目中的注意点

  1. 线程安全:上面示例非线程安全。多线程环境需要加std::mutex保护get/put。
  2. 性能坑点:std::list::splice本身O(1),但是unordered_map存在哈希冲突。高并发场景考虑使用成熟开源库,不要裸写。
  3. key/value 类型扩展:示例用int,实际可以封装模板类支持任意key、value。
  4. LRU 的局限:存在缓存污染问题:一次性批量读取大量冷数据,会把热点数据挤出去。衍生算法:LRU‑K、LFU。

开源工业级实现参考

  1. Abseil absl::LRUCache:Google开源,模板实现,支持自定义容量,工业级质量。
  2. Facebook Folly LRUCache:支持分片、统计、过期,适合服务端C++。
  3. 不建议生产环境直接复制上面手写版本,手写适合学习原理。

小结

  • LRU 淘汰最近最少使用的数据,是内存缓存最经典算法。
  • C++ 标准库没有内置LRU,底层依靠unordered_map + std::list组合实现O(1)读写。
  • 使用场景:数据库结果缓存、资源缓存、网关缓存;生产优先使用成熟开源组件。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 9:44:38

计算机组成原理第六版课后答案:版本核对与高效整理指南

简介&#xff1a;《计算机组成原理》白中英第六版课后习题答案&#xff08;PDF版&#xff09;是计算机专业核心课程的经典配套资料&#xff0c;面向本科生、考研复习者及自学者&#xff0c;用于系统巩固数据表示、运算器设计、存储层次、指令系统、CPU结构、输入输出、总线与接…

作者头像 李华
网站建设 2026/10/27 10:57:12

LangChain 上线前的安全检查:提示注入、越权和敏感数据

个人主页&#xff1a;> for_ever_love__ <&#xff08;欢迎各位大佬莅临&#x1f60a;&#xff09; 其他栏目: > 大模型开发从0到1 < 其他栏目: > iOS项目总结大全 < 其他栏目: > 我想学python了 < 其他栏目: > iOS UI < 文章目录LangChain 上线…

作者头像 李华
网站建设 2026/10/23 15:47:52

第163篇 Compose 与 View 互操作:混编的边界

先把结论放在前面:Compose 与 View 的互操作不是"能不能塞进去"的问题,而是"谁持有生命周期、谁持有状态、谁负责刷新"这三个所有权问题。 官方给的答案是 AndroidView 与 ComposeView 两个入口,桥接层负责把两套世界的约定对齐——但对齐不了的部分,就…

作者头像 李华
网站建设 2026/10/22 12:20:59

第162篇Compose 动画:animate 与 Transition

先把结论放在前面:Compose 动画的本质是"状态驱动的属性插值"——把一个 Animatable/State<Float> 从当前值插到目标值,而目标值来自状态。 所以动画的所有问题都来自一件事:状态被谁改、什么时候改、改的频率是多少。 三个必答的坑——在动画回调里改状态形…

作者头像 李华
网站建设 2026/10/25 2:58:43

CAD图纸粘贴到TinyMCE实现SVG矢量输出的完整方案

CAD图纸粘贴到TinyMCE之后能不能保持矢量输出&#xff1f;这个问题&#xff0c;我们是在一次内部文档评审会上被工程师当众问住的。当时他贴的是一张封装剖面图&#xff0c;评审大屏一放大&#xff0c;线条全是锯齿&#xff0c;焊盘和铣削标记糊成一团。原因说起来很简单&#…

作者头像 李华
网站建设 2026/10/25 14:19:12

LangGraph Plan-and-Execute:先规划再执行,别让 Agent 边想边做

个人主页&#xff1a;> for_ever_love__ <&#xff08;欢迎各位大佬莅临&#x1f60a;&#xff09; 其他栏目: > 大模型开发从0到1 < 其他栏目: > iOS项目总结大全 < 其他栏目: > 我想学python了 < 其他栏目: > iOS UI < 文章目录LangGraph Plan…

作者头像 李华