LRU 缓存的设计与实现

作者:前似锦日期:2025/11/9

目录

一、LRU 缓存的核心诉求

二、数据结构选型与设计思路

1. 双向链表:维护访问顺序的 “时间轴”

2. 哈希表:实现 key 的 O (1) 寻址

3. 组合设计:“哈希表 + 双向链表” 的协同工作

三、代码实现

1. 类结构定义

2. get 方法实现:查询并更新访问顺序

3. put 方法实现:插入、更新与容量控制

四、复杂度与边界场景分析

1. 时间复杂度

2. 边界场景处理

五、测试验证与工程价值

六、总结


在高并发与大数据场景中,缓存是提升系统性能的关键手段,而 LRU(Least Recently Used,最近最少使用) 作为最经典的缓存淘汰策略之一,其设计与实现蕴含着深刻的工程智慧。本文将从底层原理出发,详细拆解 LRU 缓存的设计思路,并提供可直接落地的 C++ 实现。

一、LRU 缓存的核心诉求

LRU 策略的核心逻辑是:当缓存容量不足时,优先淘汰 “最久未被访问” 的缓存项。要实现这一策略,我们需要解决两个关键问题:

  1. 快速查询:给定 key,需在 O (1) 时间内判断是否存在并获取 value
  2. 快速维护访问顺序:每次访问(getput)后,需将缓存项标记为 “最近使用”;当容量不足时,需快速找到并淘汰 “最久未使用” 的项。

若仅用哈希表,虽能满足 “快速查询”,但无法高效维护访问顺序;若仅用链表,查询操作会退化为 O (n),性能无法接受。因此,我们需要一种组合数据结构来突破这一困境。

二、数据结构选型与设计思路

1. 双向链表:维护访问顺序的 “时间轴”

使用双向链表存储缓存项,节点按 “最近使用” 到 “最久未使用” 的顺序排列:

  • 链表头部:最近被访问的缓存项。
  • 链表尾部:最久未被访问的缓存项(淘汰时的目标)。

双向链表的优势在于:

  • 节点移动(标记为 “最近使用”)的时间复杂度为 O (1)(只需调整指针)。
  • 淘汰操作(删除尾部节点)的时间复杂度为 O (1)。

2. 哈希表:实现 key 的 O (1) 寻址

使用 ** 哈希表(unordered_map)** 存储 key 到 “链表节点迭代器” 的映射,这样可以:

  • 快速判断 key 是否存在(O (1) 时间)。
  • 直接定位到对应的链表节点(O (1) 时间),从而避免遍历链表的开销。

3. 组合设计:“哈希表 + 双向链表” 的协同工作

哈希表的 value 是链表节点的迭代器,这样可以在 O (1) 时间内完成:

  • 查询(get:通过哈希表找到节点,返回 value 并将节点移到链表头部。
  • 插入 / 更新(put:若 key 存在则更新值并移到头部;若不存在则插入新节点到头部,若容量不足则删除尾部节点。

三、代码实现

1. 类结构定义

1class LRUCache {
2public:
3    // 构造函数:初始化缓存容量
4    LRUCache(int capacity) : _capacity(capacity) {}
5    
6    // 查询操作:O(1) 时间复杂度
7    int get(int key);
8    
9    // 插入/更新操作:O(1) 时间复杂度
10    void put(int key, int value);
11
12private:
13    // 链表节点迭代器类型(指向存储 key-value 的双向链表节点)
14    using ListIter = std::list<std::pair<int, int>>::iterator;
15    
16    std::unordered_map<int, ListIter> _hashMap;  // key -> 链表节点迭代器
17    std::list<std::pair<int, int>> _lruList;     // 双向链表,维护访问顺序
18    int _capacity;                               // 缓存最大容量
19};
20

2. get 方法实现:查询并更新访问顺序

1int LRUCache::get(int key) {
2    auto it = _hashMap.find(key);
3    if (it == _hashMap.end()) {
4        return -1;  // key 不存在
5    }
6    
7    // 将节点移到链表头部(标记为最近使用)
8    _lruList.splice(_lruList.begin(), _lruList, it->second);
9    return it->second->second;  // 返回 value
10}
11
  • splice 是双向链表的核心操作,可在 O (1) 时间内将节点移动到任意位置(这里移到头部)。

3. put 方法实现:插入、更新与容量控制

1void LRUCache::put(int key, int value) {
2    auto it = _hashMap.find(key);
3    if (it != _hashMap.end()) {
4        // 情况1:key 已存在,更新 value 并标记为最近使用
5        ListIter node = it->second;
6        node->second = value;  // 更新 value
7        _lruList.splice(_lruList.begin(), _lruList, node);
8    } else {
9        // 情况2:key 不存在,插入新节点
10        // 若容量已满,淘汰最久未使用的节点(链表尾部)
11        if (_lruList.size() == _capacity) {
12            auto tail = _lruList.back();
13            _hashMap.erase(tail.first);  // 从哈希表中删除
14            _lruList.pop_back();         // 从链表中删除
15        }
16        // 插入新节点到链表头部
17        _lruList.push_front({key, value});
18        _hashMap[key] = _lruList.begin();  // 哈希表记录映射
19    }
20}
21
  • 插入新节点前,若容量已满,需先删除链表尾部的 “最久未使用” 节点,并同步从哈希表中移除对应的 key

四、复杂度与边界场景分析

1. 时间复杂度

  • get 操作:O (1)(哈希表查询 + 链表节点移动)。
  • put 操作:O (1)(哈希表插入 / 删除 + 链表节点移动 / 删除)。

这一复杂度满足了题目中 “O (1) 平均时间复杂度” 的要求。

2. 边界场景处理

  • 空缓存 / 空 keyget 操作返回 -1。
  • 容量为 1:每次插入都会淘汰之前的节点,保证缓存中始终只有最新的一个项。
  • 重复 put 同一 key:会更新其 value 并将其标记为 “最近使用”。

五、测试验证与工程价值

我们通过一个典型场景验证实现的正确性:

1int main() {
2    LRUCache cache(2);  // 容量为 2 的 LRU 缓存
3    cache.put(1, 1);
4    cache.put(2, 2);
5    cout << cache.get(1) << endl;  // 输出 1(1 被标记为最近使用)
6    cache.put(3, 3);    // 容量已满,淘汰最久未使用的 2
7    cout << cache.get(2) << endl;  // 输出 -1(2 已被淘汰)
8    cache.put(4, 4);    // 容量已满,淘汰最久未使用的 1
9    cout << cache.get(1) << endl;  // 输出 -1(1 已被淘汰)
10    cout << cache.get(3) << endl;  // 输出 3(3 被标记为最近使用)
11    cout << cache.get(4) << endl;  // 输出 4(4 被标记为最近使用)
12    return 0;
13}
14

输出结果与预期完全一致,证明了实现的正确性。

六、总结

LRU 缓存的设计是 “数据结构协同作战” 的经典案例:双向链表解决了 “访问顺序维护” 的问题,哈希表解决了 “快速寻址” 的问题,二者结合让 LRU 策略在 O (1) 时间复杂度下高效运行。

这一设计思路不仅适用于算法题,更在工业级缓存系统(如 Redis 的缓存策略)中被广泛应用。理解其底层逻辑,有助于我们在复杂业务场景中设计更高效的缓存方案。


LRU 缓存的设计与实现》 是转载文章,点击查看原文


相关推荐


Less-8 GET-Blind-Boolean Based-Single Quotes
泷羽Sec-静安2025/11/7

GET-盲注-基于布尔值-单引号 Less-8 代码分析 关键特征对比 特征Less-5Less-8SQL结构id='$id'id='$id'成功时“You are in”“You are in”失败时显示错误 mysql_error()什么都不显示注入类型报错注入/布尔盲注纯布尔盲注核心区别(关键!) // Less-5 else { echo 'You have an error in your SQL syntax'; print_r(mysql_error()); /


Python 的内置函数 format
IMPYLH2025/11/2

Python 内建函数列表 > Python 的内置函数 format Python 的内置函数 format() 是一个功能强大的字符串格式化工具,它提供了灵活且可读性强的格式化方式。该函数主要通过两种形式使用: 作为字符串对象的方法: "格式化字符串".format(参数) 这是最常见的用法,在字符串内部使用 {} 作为占位符,然后通过 format() 方法传入参数进行替换。 作为独立的内置函数: format(value, format_spec) 这种形式主要用于对单个值进


2025年组件化开发这样做,效率提升300%
良山有风来2025/10/31

你是不是还在重复写着相似的代码?每次产品经理说要改个按钮样式,你都得在几十个文件里翻来翻去?明明是个小改动,却要花大半天时间? 别担心,这篇文章就是来拯救你的。我会带你彻底搞懂现代前端框架的组件化开发,从基础概念到实战技巧,再到2025年的最新趋势。读完本文,你将拥有一套完整的组件化思维,开发效率至少提升3倍! 什么是组件化开发? 简单来说,组件化就是把页面拆分成一个个独立的小模块。就像搭乐高积木一样,每个组件都是独立的积木块,你可以随意组合、重复使用。 想想你每天写的代码,是不是经常遇到这样的


__工艺数据管理的范式转变:金仓数据库替代MongoDB实操实践__
金仓拾光集2025/10/28

——一位资深DBA的国产化迁移手记 作者:小马哥 | 某大型制造企业数据库架构师,10年+核心系统数据库运维与信创改造经验 一、引言:当半结构化工艺数据遇上国产信创浪潮 在智能制造加速推进的今天,工艺数据已成为工厂数字化的核心资产。从设备传感器采集的实时参数,到生产流程中的质检记录、工单变更日志,这些数据往往具有高度的半结构化特征——字段动态变化、嵌套层级深、写入高频且查询复杂。 过去,许多制造企业选择MongoDB作为这类数据的存储引擎,凭借其灵活的BSON文档模型和横向扩展能力,快速响


【2026计算机毕业设计】基于Django的新闻资讯平台的设计与实现
计算机毕业设计小帅2025/10/25

🔍 【关注我,毕业设计不迷茫】| 6年辅导经验 | 帮助1200+学子顺利毕业 |xiaoshuaibishe 大家好,我是程序员小帅,一名专注于计算机毕业设计全流程辅导的技术博主。专注JavaWeb,我深耕毕设领域6年,累计输出1200+原创项目案例,辅导成功率接近100%。如果你正在为选题、代码、论文或答辩发愁,这里能给你最落地的解决方案 一、摘要 21世纪是信息的时代,是网络的时代,进入信息社会高速发展的时代,数字化革命给所有领域带来新的改变。传统的报纸杂志已经远远满足不了人们的需求,人


5G无人机用单兵图传设备 5G单兵图传 无线图传 无人机图传
无线图像传输研究探索2025/10/23

在应急救援、执法执勤等诸多场景中,信息的实时传递与高效沟通至关重要。单兵图传设备作为一种先进的通信工具,正发挥着无可替代的作用。 单兵图传(17354349498) 一、设备概述 WB7000-DB-5G 高清视频终端采用嵌入式系统架构,采用高性能 H.265 编解码处理器设计。设备支持视频采集、编码压缩、传输、双 向对讲功能。 设备基于先进的 H.265 视频编码技术和 5G 无线信道捆绑传输技 术开发的新一代产品。支持支持 5G、4G 网络模式,采用 H.265(HEVC) 超低


谷歌发布首个隐私安全模型VaultGemma
强哥之神2025/10/22

谷歌AI研究团队与DeepMind刚刚发布了 VaultGemma 1B —— 这是目前规模最大的、完全在差分隐私(Differential Privacy, DP)保障下从头训练的开源大语言模型。它不是在已有模型基础上做微调,而是从预训练阶段就嵌入了隐私保护机制。这个尝试,让我觉得有点像在一片风沙中种树——既要长得高,又不能伤根。   我们都知道,现在的LLM(大语言模型)训练数据动辄万亿token,来自整个互联网。但问题也随之而来:模型会“记住”训练数据中的敏感信息,甚至能被攻击者通过提示


校园交友|基于SprinBoot+vue的校园交友网站(源码+数据库+文档)
老华带你飞2025/10/20

校园交友网站 目录 基于SprinBoot+vue的校园交友网站 一、前言 二、系统设计 三、系统功能设计  1系统功能模块 2后台功能模块 5.2.1管理员功能模块 5.2.2用户功能模块 四、数据库设计  五、核心代码  六、论文参考 七、最新计算机毕设选题推荐 八、源码获取: 博主介绍:✌️大厂码农|毕设布道师,阿里云开发社区乘风者计划专家博主,CSDN平台Java领域优质创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。✌️ 主要项


使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 18--测试框架Pytest基础 2--插件和参数化
我的xiaodoujiao2025/10/19

测试学习记录,仅供参考! Pytest 框架 七、pytest 丰富的插件系统 可以去安装外部的插件,去使用 pytest 来执行插件;这就是为什么选择使用 pytest 框架的原因,因为 pytest 里面有丰富的插件系统,而 unittest 是没有这些插件的;所以 pytest 要比 unittest 测试框架更加灵活; 例如: 当测试用例很多的时候,可以使用并发执行,可以使用多进程/多线程去执行测试用例,提高测试效率; 测试用例失败重跑插件;在执行测试用例时,由于网络


从零构建量化学习工具:动量策略(Momentum Strategy)
叶梅树2025/10/18

知识点介绍 动量策略是量化交易的核心知识点之一,它基于“强者恒强、弱者恒弱”的市场假设:过去表现好的资产(价格上涨)未来继续上涨,表现差的继续下跌。核心公式:动量 = (当前价 - N期前价) / N期前价(N通常12月/20日)。交易规则:动量>0买入、<0卖出,或排名选TopK股。 优势:简单有效,捕捉趋势(A股牛市强)。缺点:趋势反转时大回撤(如2022熊市)。入门公式:Sharpe比率 = (策略收益 - 无风险率) / 波动率(>1好)。实践:用历史数据回测,结合RSI避超买。 案例说

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0