C++实现LRU缓存(新手入门详解)

LRU的概念
LRU(Least Recently Used,最近最少使用)是一种常用的缓存淘汰策略,主要目的是在缓存空间有限的情况下,优先淘汰那些最长时间没有被访问的数据项。LRU 策略的核心思想是:
-
缓存空间有限:缓存只能存储一定数量的数据项。
-
淘汰最不常用的数据:当缓存满时,优先淘汰那些最近最少被访问的数据项。
-
访问记录:每次数据项被访问时,都会更新其访问记录,使得最近访问的数据项保留在缓存中。
-
数据替换:当需要加载新数据项到缓存中,但缓存已满时,会根据LRU策略淘汰一个或多个数据项,为新数据项腾出空间。
-
动态调整:随着数据访问模式的变化,LRU策略可以动态调整缓存中的数据项,以适应访问模式的变化。
在实现LRU缓存时,通常会使用数据结构如哈希表和双向链表。哈希表用于快速定位缓存中的数据项,而双向链表则用于维护数据项的访问顺序。每次访问数据项时,都会将其移动到链表的头部,表示它是最近被访问的。当需要淘汰数据时,直接从链表的尾部开始淘汰即可。
LRU策略在许多场景中都非常有用,比如操作系统的页面置换、数据库的查询缓存、Web服务器的页面缓存等。它可以帮助系统更有效地利用有限的缓存资源,提高系统的整体性能。


别急,我们先学实现LRU要用的哈希表和双向链表
哈希表(unordered_map)
在C++中,unordered_map 是标准模板库(STL)中的一个关联容器,它基于哈希表的实现。它存储了键值对,允许通过键快速访问和修改值。unordered_map 提供了平均常数时间复杂度的访问、插入和删除操作。
主要特性
- 基于哈希表:通过哈希函数将键映射到存储位置,实现快速查找。
- 键不重复:每个键在容器中是唯一的。
- 无序存储:元素的存储顺序不依赖于插入顺序,因此迭代器的遍历顺序可能与插入顺序不同。
常用操作
-
构造和初始化:
unordered_map():创建一个空的unordered_map。unordered_map(initializer_list<value_type>):使用初始化列表创建unordered_map。
-
插入操作:
insert(value_type):插入一个键值对。insert(initializer_list<value_type>):插入多个键值对。
-
访问操作:
operator[]:通过键访问对应的值,如果键不存在,则插入一个新元素。at(key):通过键访问对应的值,如果键不存在,则抛出std::out_of_range异常。
-
查找操作:
find(key):查找键是否存在,返回一个迭代器。count(key):返回键出现的次数(对于unordered_map总是返回 0 或 1)。
-
删除操作:
erase(it):删除迭代器it指向的元素。erase(first, last):删除从first到last(不包括last)范围内的所有元素。erase(key):删除指定键的所有元素。
-
大小和容量:
size():返回容器中元素的数量。empty():如果容器为空,返回true。
-
迭代器:
begin():返回指向容器开始的迭代器。end():返回指向容器结束的迭代器。
示例代码
以下是使用 unordered_map 的一个简单示例:
#include <iostream>
#include <unordered_map>int main() {// 创建一个 unordered_map,键为 int,值为 stringunordered_map<int, string> umap;// 插入元素umap[1] = "one";umap[2] = "two";umap[3] = "three";// 访问并打印元素for (const auto& pair : umap) {std::cout << pair.first << ": " << pair.second << std::endl;}// 访问特定键的值try {std::cout << "Value for key 2: " << umap.at(2) << std::endl;} catch (const std::out_of_range& e) {std::cerr << e.what() << std::endl;}// 查找键是否存在auto it = umap.find(3);if (it != umap.end()) {std::cout << "Key 3 found, value: " << it->second << std::endl;}// 删除元素umap.erase(2);std::cout << "After erasing key 2:" << std::endl;for (const auto& pair : umap) {std::cout << pair.first << ": " << pair.second << std::endl;}return 0;
}
输出:
1: one
2: two
3: three
Value for key 2: two
Key 3 found, value: three
After erasing key 2:
1: one
3: three
在这个示例中:
- 创建了一个
unordered_map并插入了一些键值对。 - 遍历并打印了
unordered_map中的所有元素。 - 使用
at()方法安全地访问特定键的值。 - 使用
find()方法查找键是否存在,并访问对应的值。 - 使用
erase()方法删除了键为 2 的元素,并再次打印了剩余的元素。
双向链表(list)
在C++中,list 是标准模板库(STL)中的一个容器类,它提供了双向链表的实现。与数组或向量(vector)不同,list 允许在任意位置高效地插入和删除元素,而不需要移动其他元素。
以下是 list 的一些主要特性和常用操作:
特性
- 双向链表:每个元素都是链表中的一个节点,可以从前向后或从后向前遍历。
- 动态大小:
list的大小可以根据需要动态变化,不需要预先定义大小。 - 插入和删除操作:可以在常数时间内在任意位置插入或删除元素,不需要像
vector那样移动其他元素。
常用操作
-
插入操作:
push_front(value):在链表头部插入一个元素。push_back(value):在链表尾部插入一个元素。insert(position, value):在指定位置插入一个元素。insert(position, n, value):在指定位置插入n个相同的元素。insert(position, first, last):在指定位置插入一个范围内的元素。
-
删除操作:
pop_front():删除链表头部的元素。pop_back():删除链表尾部的元素。erase(position):删除指定位置的元素。erase(first, last):删除从first到last(不包括last)范围内的所有元素。
-
访问操作:
front():返回链表头部的元素。back():返回链表尾部的元素。
-
迭代器:
begin():返回指向链表头部的迭代器。end():返回指向链表尾部的迭代器。
-
大小和容量:
size():返回链表中元素的数量。empty():如果链表为空,返回true。
示例代码
以下是使用 list 的一个简单示例:
#include <iostream>
#include <list>int main() {list<int> myList;// 向链表中添加元素myList.push_back(10);myList.push_back(20);myList.push_front(5);// 访问并打印链表中的元素for (list<int>::iterator it = myList.begin(); it != myList.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 删除头部元素myList.pop_front();std::cout << "After popping front: ";for (auto it = myList.begin(); it != myList.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 删除尾部元素myList.pop_back();std::cout << "After popping back: ";for (auto it = myList.begin(); it != myList.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;return 0;
}
输出:
5 10 20
After popping front: 10 20
After popping back: 10
在这个示例中,我们创建了一个 list 并添加了一些整数元素。然后,我们遍历并打印链表中的元素,删除头部和尾部的元素,并再次打印链表中的元素。

到这里,你已经掌握实现LRU缓存的两个条件了,马上你就要成功了!!!

真的,不信你往下看!
LRU缓存(C++)
#include <iostream>
#include <list>
#include <unordered_map>// 使用 using namespace std; 来简化代码,避免重复书写 std:: 前缀
using namespace std;// LRUCache 类定义
class LRUCache {
private:int capacity; // 缓存的容量list<int> keys; // 使用双向链表存储键,保持访问顺序unordered_map<int, pair<int, list<int>::iterator>> cache; // 存储键值对和对应的链表迭代器public:// 构造函数,初始化缓存容量LRUCache(int capacity) : capacity(capacity) {}// 获取缓存中键对应的值int get(int key) {auto it = cache.find(key);if (it == cache.end()) {return -1; // 如果键不存在,返回 -1}// 更新访问顺序,将该键移动到链表头部keys.erase(it->second.second);keys.push_front(key);it->second.second = keys.begin();return it->second.first; // 返回键对应的值}// 插入或更新缓存中的键值对void put(int key, int value) {if (cache.size() >= capacity && cache.find(key) == cache.end()) {// 如果缓存已满且键不存在,淘汰最不常用的键(链表尾部的键)auto last = keys.back();cache.erase(cache.find(last));keys.pop_back();}// 插入或更新键值对,并更新访问顺序cache[key] = {value, keys.insert(keys.begin(), key)};}
};int main() {// 创建一个容量为 2 的 LRU 缓存LRUCache cache(2);// 插入键值对 (1, 1)cache.put(1, 1);// 访问键 1,输出其值cout << "get(1) = " << cache.get(1) << endl; // 返回 1// 插入键值对 (2, 2)cache.put(2, 2);// 访问键 2,输出其值cout << "get(2) = " << cache.get(2) << endl; // 返回 2// 插入键值对 (3, 3),由于缓存已满,键 1 被淘汰cache.put(3, 3);// 访问键 1,由于已被淘汰,返回 -1cout << "get(1) = " << cache.get(1) << endl; // 返回 -1// 访问键 3,输出其值cout << "get(3) = " << cache.get(3) << endl; // 返回 3// 插入键值对 (4, 4),由于缓存已满,键 2 被淘汰cache.put(4, 4);// 访问键 1,由于已被淘汰,返回 -1cout << "get(1) = " << cache.get(1) << endl; // 返回 -1// 访问键 3,输出其值cout << "get(3) = " << cache.get(3) << endl; // 返回 3// 访问键 2,由于已被淘汰,返回 -1cout << "get(2) = " << cache.get(2) << endl; // 返回 -1// 访问键 4,输出其值cout << "get(4) = " << cache.get(4) << endl; // 返回 4return 0;
}
这段代码首先定义了一个 LRUCache 类,该类使用 unordered_map 和 list 来实现 LRU 缓存机制。get 方法用于获取缓存中的值,如果键存在,则返回其值并更新访问顺序;如果键不存在,则返回 -1。put 方法用于插入或更新缓存中的键值对,如果缓存已满,则淘汰最不常用的键(链表尾部的键)。在 main 函数中,创建了一个 LRUCache 对象并进行了一些操作来演示其功能。

什么?看不懂?没关系,结合下面的过程看,你应该就明白了!
初始化状态
Cache: {}
Keys: []
执行 cache.put(1, 1)
Cache: {1: (1, it1)}
Keys: [1]
执行 cache.put(2, 2)
Cache: {1: (1, it1), 2: (2, it2)}
Keys: [2, 1] (2 最近使用,1 最少使用)
执行 cache.put(3, 3)
- 缓存已满,淘汰键 1
Cache: {2: (2, it2), 3: (3, it3)}
Keys: [3, 2] (3 最近使用,2 次之)
执行 cache.get(1)
- 键 1 不存在,返回 -1
Cache: {2: (2, it2), 3: (3, it3)}
Keys: [3, 2]
执行 cache.get(3)
- 键 3 存在,返回 3,并更新为最近使用
Cache: {2: (2, it2), 3: (3, it3)}
Keys: [3, 2]
执行 cache.put(4, 4)
- 缓存已满,淘汰键 2
Cache: {3: (3, it3), 4: (4, it4)}
Keys: [4, 3] (4 最近使用,3 次之)
执行 cache.get(1)
- 键 1 不存在,返回 -1
Cache: {3: (3, it3), 4: (4, it4)}
Keys: [4, 3]
执行 cache.get(3)
- 键 3 存在,返回 3,并更新为最近使用
Cache: {3: (3, it3), 4: (4, it4)}
Keys: [3, 4]
执行 cache.get(2)
- 键 2 不存在,返回 -1
Cache: {3: (3, it3), 4: (4, it4)}
Keys: [3, 4]
执行 cache.get(4)
- 键 4 存在,返回 4,并更新为最近使用
Cache: {3: (3, it3), 4: (4, it4)}
Keys: [4, 3]

至此,你就算没有台明白,也一定了解LRU了。收藏可以方便下次巩固哦!!!!

相关文章:
C++实现LRU缓存(新手入门详解)
LRU的概念 LRU(Least Recently Used,最近最少使用)是一种常用的缓存淘汰策略,主要目的是在缓存空间有限的情况下,优先淘汰那些最长时间没有被访问的数据项。LRU 策略的核心思想是: 缓存空间有限࿱…...
汇昌联信数字做拼多多运营实力好吗?
汇昌联信数字在拼多多运营方面的实力如何?汇昌联信数字作为一家专注于电子商务运营服务的公司,其在拼多多平台的运营能力是值得关注的。根据市场反馈和客户评价,汇昌联信数字在拼多多的运营实力表现良好,能够为客户提供专业的店铺管理、产品…...
【云原生】Prometheus 服务自动发现使用详解
目录 一、前言 二、Prometheus常规服务监控使用现状 2.1 Prometheus监控架构图 2.2 Prometheus服务自动发现的解决方案 三、Prometheus服务自动发现介绍 3.1 什么是Prometheus服务自动发现 3.2 Prometheus自动服务发现策略 3.3 Prometheus自动服务发现应用…...
(十九)原生js案例之h5地里位置信息与高德地图的初使用
h5 地里位置信息 1. 获取当前位置信息 window.onload function () {const oBtn document.querySelector("#btn");const oBox document.querySelector("#box");oBtn.onclick function () {window.navigator.geolocation.getCurrentPosition(function (…...
三、基础语法2(30小时精通C++和外挂实战)
三、基础语法2(30小时精通C和外挂实战) B-02内联函数B-04内联函数与宏B-05_constB-06引用B-07引用的本质B-08-汇编1-X86-X64汇编B-09-汇编2-内联汇编B-10-汇编3-MOV指令C-02-汇编5-其他常见指令C-05-汇编8-反汇编分析C-07-const引用、特点 B-02内联函数 …...
gitee设置ssh公钥密码频繁密码验证
gitee中可以创建私有项目,但是在clone或者push都需要输入密码, 比较繁琐。 公钥则可以解决该问题,将私钥放在本地,公钥放在gitee上,当对项目进行操作时带有的私钥会在gitee和公钥进行验证,避免了手动输入密…...
BGP选路之Next Hop
原理概述 当一台BGP路由器中存在多条去往同一目标网络的BGP路由时,BGP协议会对这些BGP路由的属性进行比较,以确定出去往该目标网络的最优BGP路由,然后将该最优BGP路由与去往同一目标网络的其他协议路由进行比较,从而决定是否将该最优BGP路由放进P路由表中…...
牛客14666(优先屏障) + 牛客14847(Masha与老鼠)
文章目录 写在前面14666-优先屏障思路编程 14847-Masha与老鼠思路编程 写在前面 昨天刷的这两道题写了很久,特别是Masha与老鼠这道题,写了都快3个小时,主要还是理解代码逻辑有点难,不过写完之后感觉收获挺大的,给我以…...
Git下载与安装
下载网址:https://git-scm.com/downloads 下载之后开始安装 选择安装路径,next 选择需要安装的组件,这里默认即可,next 选择菜单文件夹,这里默认即可,next 选择默认编辑器,默认推荐的即可&…...
创建vue2/vue3项目
目录 创建一个Vue2项目创建一个Vue3项目 创建一个Vue2项目 ## 安装Vue-Cli : npm install -g vue/cli // Vue CLI 4.x 需要 Node.js v8.9 或更高版本 (推荐 v10 以上)vue --version // 检测版本是否正确## 创建一个项目: vue create hello-world // hel…...
IOS七层模型对应的网络协议和物理设备
以下是网络模型、对应的协议以及对应的物理设备的表格总结: 网络模型层次主要功能对应协议对应物理设备物理层透明的传输比特流,确定机械及电气规范RS-232、V.35、RJ-45、FDDI等中继器、集线器、网线、调制解调器、网卡数据链路层将比特组装成帧和点到点…...
论文复现:Predictive Control of Networked Multiagent Systems via Cloud Computing
Predictive Control of Networked Multiagent Systems via Cloud Computing论文复现 文章目录 Predictive Control of Networked Multiagent Systems via Cloud Computing论文复现论文摘要系统参数初始化系统模型观测器预测过程控制器设计系统的整体框图仿真结果 论文摘要 翻译…...
JSON 文件存储
JSON 全称为: JavaScript Object Notation 也就是 javaScript 对象标记,通过对象和数组的组合来表示数据, 虽然构造简洁,但是结构化程度非常高, 是一种轻量级的数据交换格式 对象和数组 在 JavaScript 语言中&#…...
python——pynput
pynput 是一个 Python 库,用于控制和监听键盘与鼠标输入。它在 Windows、macOS 和 Linux 上都可以工作,为用户提供了一个跨平台的输入事件处理方式。pynput 包含两个主要模块:keyboard 和 mouse,分别用于处理键盘和鼠标事件。 主…...
[用AI日进斗金系列]用码上飞在企微接单开发一个项目管理系统!
今天是【日进斗金】系列的第二期文章。 先给不了解这个系列的朋友们介绍一下,在这个系列的文章中,我们将会在企微的工作台的“需求发布页面”中寻找有软件开发需求的用户 并通过自研的L4级自动化智能软件开发平台「码上飞CodeFlying」让AI生成应用以解…...
《JavaEE篇》--多线程(2)
《JavaEE篇》--多线程(1) 线程安全 线程不安全 我们先来观察一个线程不安全的案例: public class Demo {private static int count 0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() -> {//让count自增5W次…...
防爆智能手机如何助力电气行业保驾护航?
在电气行业的智能化转型浪潮中,防爆智能手机以其强大的数据处理能力、实时通讯功能及高度集成的安全特性,正成为保障电力网络稳定运行、预防安全隐患的得力助手。 防爆智能手机在电气行业中发挥着重要的保驾护航作用,主要体现在以下几个方面&…...
24.7.24数组|那几个课后得做的题
1、对长整形数据进行反转 2、对字符串进行反转 一、题目地址: 1. 实现一个函数atoi,使其能够将字符串转换整数 (Leetcode 8/中等). - 力扣(LeetCode) 2. 颠倒给定的32位无符号整数的二进制位(Leetcode 190/简单&…...
03Spring底层架构核心概念解析
为了感谢罕哥对我工作的帮助,特此记录下学习过程,期待成为和罕哥一样优秀的人 时间:2024.7.13 内容:spring源码课程3学习记录 一、BeanDefinition BeanDefinition表示Bean的定义,BeanDefinition中存在很多属性用来…...
Vue学习---vue 防抖处理函数,是处理什么场景
Vue防抖处理函数是用来处理在快速连续操作中,只执行最后一次操作的情况。 例如,在输入框输入时,我们可能希望只在用户完成输入后进行处理,而不是在每次键入时都处理。(n秒后触发一次) 以下是一个简单的Vue防抖处理函数的例子&am…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
