LRU Cache
文章目录
- 1. 什么是LRU Cache
- 2. LRU Cache的实现
- 3. LRU Cache的OJ
- 题目分析
- AC代码
1. 什么是LRU Cache
LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法。
什么是Cache?
狭义的Cache指的是位于CPU和主存间的快速RAM, 通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。 广义上的Cache指的是位于速度相差较大的两种硬件之间, 用于协调两者数据传输速度差异的结构。除了CPU与主存之间有Cache, 内存与硬盘之间也有Cache,乃至在硬盘与网络之间也有某种意义上的Cache── 称为Internet临时文件夹或网络内容缓存等。
而Cache的容量有限,那如果cache满了怎么办?
当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容。
那应该选取那一部分的内容和新内容进行替换呢?这就涉及到cache的替换算法,而LRU Cache就是cache替换算法中的一种!
LRU Cache 的替换原则就是将最近最少使用的内容替换掉。其实,LRU译成最久未使用会更形象, 因为该算法每次替换掉的就是一段时间内最久没有使用过的内容。
2. LRU Cache的实现
那要实现一个LRU Cache其实并不难,方法和思路有很多;但是想要实现一个高效(所有操作都是O(1)
)的LRU Cache是有难度的
实现LRU Cache的方法和思路很多,但是要保持高效实现O(1)的put和get,那么使用双向链表和哈希表的搭配是最高效和经典的。
使用双向链表是因为双向链表可以实现任意位置O(1)的插入和删除,使用哈希表是因为哈希表的增删查改也是O(1)。
那具体到底应该应该怎么做呢?
下面我们借助LeetCode上的一道OJ来给大家进行一个详细的讲解。
3. LRU Cache的OJ
题目链接: link
我们来看一下题目:
大家可以自己先看一下题目,我们看到题目中要求函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
那我们来分析一下要如何实现
题目分析
题目要求我们实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
这个 LRUCache 类的要求是这样的:
那我们要如何实现呢?
其实分析完题目我们很容易能想到用哈希表,那当然C++里面我们就用unordered_map去搞,那这样的话get函数(其实就是查找嘛)就能达到O(1);然后put呢,put也是要查找嘛,找到就更新,找不到就插入。那这样就也是O(1)。
但是呢我们真正还要考虑还有——如果插入操作导致关键字数量超过 capacity ,我们此时就要进行LRU替换,则应该 逐出 最久未使用的关键字。
那我们要如何实现LRU的替换呢?满的话应该逐出谁啊?如何找到最久未被使用的那个呢?
🆗,那上面也提到了,使用双向链表和哈希表的搭配是最高效和经典的:
我们再搞一个list(list底层就是带头双向循环链表),让它里面也存储数据(相当于数据存储两份),然后我们可以默认认为尾部的那个元素就是最近最少用(最久未被使用)的(按头部实现也可)。
然后如果有新插入的元素或者被访问(get一个已有的值)的元素我就把它移到链表头部。
这样我们需要替换的时候,那么链表尾部的那个就是最久未被使用的那个。
但是呢?
如果我们就这样设计的话,还存在一个问题。
就是更新的时候其实复杂度是O(N)。
为什么呢?
更新的情况就是调用put先在哈希表里面查找到key是已存在的,那然后我们要修改,哈希表里面我找到这个就可以直接修改。
但是,在list里面我们是不是也要修改啊,因为我们替换的时候找最久未被使用的那个值就是要从list里面找呢。
但是要修改list的话我们知不知道当前要修改的这个元素是list里面的哪一个元素?
是不知道的,所以还得遍历list去找。找到之后更新一下,然后把它移到头部去,更新顺序。
那这里遍历查找的话不就是O(N)了嘛。
那这个问题我们如何解决一下呢?
如何做到在哈希表里面找到这个key之后,就直接能获取到他在list中的位置,而不需要去遍历查找呢?
那这里有一个非常巧的设计是这样来解决的:
还是list和unordered_map搭配。
list里面呢还是存key-value的键值对pair。然后哈希表里面key还是要存的,但是不再像上面写的那样直接存key对应的数据value,而是存这个key对应的元素在list里面的对应的迭代器。(那这样真正的数据就只存在list里面)
那这样的话如果更新的话,首先我们在哈希表里面找到key,然后通过它里面存的该元素在list中的迭代器,就可以直接修改list里面存放的数据。
然后再把它放到链表头部(两种方法,后面会提到)。
当然list<pair<int,int>>::iterator
这个类型比较长,我们也可以typedef一下
那下面我们来尝试写一下代码:
AC代码
那首先我们应该还要再增加一个成员变量:
即cache的容量capactity,因为有时候我们还需要判断cache满不满。
然后我们来逐一实现一下它的3个成员函数:
首先是它的构造函数LRUCache:
那这个很简单,就是初始化一下capacity就行了。
剩下两个成员变量是自定义类型,有默认构造。
那然后是get函数:
get呢也很简单,就是通过key判断在不在嘛。
如果在的话,就返回key对应的值;如果不在,返回-1。
那我们就可以直接调用unordered_map的find函数,根据find的返回结果判断,如果找到了,我们要返回什么呢?
🆗,首先这里的ret是啥啊,这里find返回的是迭代器,找到的话返回的就是key对应的这个元素的迭代器。
那我们要返回这个key对应的那个有效的值,那真正的数据是存在list里面的。
我们通过谁可以找到list里面存储的这个key对应的元素呢?
🆗,现在unordered_map里面的value存的是list里面这个key对应元素的迭代器(有点绕了这里😂)。
所以ret->second就是list里面这个元素的迭代器,但是我们想要拿到的是这个元素的key对应的值,即ret->second->second
,这里要取两次second。
🆗,那这样就完了嘛?
还没有。
如果get的时候成功找到了这个数据,那相当于访问了它,那cache里面存储数据的顺序是不是就要调整了。是不是要把这个刚被访问的元素放到链表头部啊。
那如何在list里面调整这个顺序呢?我们上面提到有两种方式:
首先第一种方法我们可以先把这个元素删除掉,然后在头插到头部。
但是这样的话记得还要更新一下unordered_map里面存的对应的那个迭代器,因为删完之后这个迭代器就失效了。之前的文章我们模拟实现过list,我们知道list的迭代器其实就是对结点指针的封装嘛,这个结点删除的话它这个结点指针指向的空间就释放了。
所以这种方法好像有点麻烦。
那我就可以考虑用另外一种——直接调用list的splice
接口转移结点。
那splice 这个函数其实我们之前也有提到过
它可以把一个链表的一部分转移到另一个链表(当然也可以是同一个链表直接进行转移)
所以我们就可以直接调用splice将这个结点转移到list的头部。
那最后就剩下put:
那put的话呢无非就两种操作
如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。
当然插入的时候需要判断:
如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字,然后插入新值。
另外不论是插入还是更新,都应该把插入或更新的值放到链表头部。
那我们来写一下代码:
🆗,那到这里就完事了。
我们来提交一下:
没有问题!
完整代码:
class LRUCache {
public:LRUCache(int capacity):_capacity(capacity){}int get(int key) {auto ret=_hashmap.find(key);if(ret!=_hashmap.end()){LtIter it=ret->second;//将it对应的结点转移到链表头部_LRUList.splice(_LRUList.begin(),_LRUList,it);return it->second;}return -1;}void put(int key, int value) {auto ret=_hashmap.find(key);//如果找到,更新值if(ret!=_hashmap.end()){LtIter it=ret->second;it->second=value;//将it对应的结点转移到链表头部_LRUList.splice(_LRUList.begin(),_LRUList,it);}else//找不到,插入{//如果满了需要先删除最久未使用的值if(_capacity==_hashmap.size()){pair<int,int> back=_LRUList.back();_hashmap.erase(back.first);_LRUList.pop_back();}//插入_LRUList.push_front(make_pair(key,value));_hashmap[key]=_LRUList.begin();}}
private:typedef list<pair<int,int>>::iterator LtIter;//hash做到查找更新/插入是O(1)unordered_map<int, LtIter> _hashmap;//LRU 默认链表尾部的是最久未被使用的list<pair<int, int>> _LRUList;size_t _capacity;
};
那我们这篇文章就到这里…
相关文章:

LRU Cache
文章目录 1. 什么是LRU Cache2. LRU Cache的实现3. LRU Cache的OJ题目分析AC代码 1. 什么是LRU Cache LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法。 什么是Cache? 狭义的Cache指的是位于CPU和主存间的快速RAM…...

软件测试面试题整理
软件测试的几个阶段 在进行Beta测试之前和之后,通常会进行以下几种测试: 内部测试(Internal Testing) 在Beta测试之前,开发团队会进行内部测试,对软件进行全面的测试。这个阶段包括单元测试、集成测试和系…...

C++三剑客之std::variant(二):深入剖析
目录 1.概述 2.辅助类介绍 2.1.std::negation 2.2.std::conjunction 2.3.std::is_destructible 2.4.std::is_object 2.5.is_default_constructible 2.6.std::is_trivially_destructible 2.7.std::in_place_type和std::in_place_index 3.原理分析 3.1.存储分析 3.2.…...

实验一 安装和使用Oracle数据库
🕺作者: 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 😘欢迎关注:👍点赞🙌收藏✍️留言 🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的…...
软件工程研究生后期总结
写这篇随笔的时候,我已经处于研究生阶段的后期,只剩下一个硕论答辩即可结束研究生生涯。趁有闲暇时间,我希望可以从实习、兼职、论文和求职等几个角度重新整理一下研究生后期的工作和收获,以及对未来工作和生活做出展望。 首先简…...

Java爬虫爬取图片壁纸
Java爬虫 以sougou图片为例:https://pic.sogou.com/ JDK17、SpringBoot3.2.X、hutool5.8.24实现Java爬虫,爬取页面图片 项目介绍 开发工具:IDEA2023.2.5 JDK:Java17 SpringBoot:3.2.x 通过 SpringBoot 快速构建开发环境…...

红队打靶练习:HOLYNIX: V1
目录 信息收集 1、arp 2、netdiscover 3、nmap 4、nikto whatweb 目录探测 1、gobuster 2、dirsearch 3、dirb 4、feroxbuster WEB sqlmap 1、爆库 2、爆表 3、爆列 4、爆字段 后台登录 1、文件上传 2、文件包含 3、越权漏洞 反弹shell 提权 总结 信息…...

elasticsearch[二]-DSL查询语法:全文检索、精准查询(term/range)、地理坐标查询(矩阵、范围)、复合查询(相关性算法)、布尔查询
ES-DSL查询语法(全文检索、精准查询、地理坐标查询) 1.DSL查询文档 elasticsearch 的查询依然是基于 JSON 风格的 DSL 来实现的。 1.1.DSL 查询分类 Elasticsearch 提供了基于 JSON 的 DSL(Domain Specific Language)来定义查…...

Microsoft Word 设置底纹
Microsoft Word 设置底纹 References 打开文档页面,选中特定段落或全部文档 在“段落”中单击“边框”下三角按钮 在列表中选择“边框和底纹”选项 在“边框和底纹”对话框中单击“底纹”选项卡 在图案样式和图案颜色列表中设置合适颜色的底纹,单击“确…...

【大数据】Flink 详解(九):SQL 篇 Ⅱ
《Flink 详解》系列(已完结),共包含以下 10 10 10 篇文章: 【大数据】Flink 详解(一):基础篇【大数据】Flink 详解(二):核心篇 Ⅰ【大数据】Flink 详解&…...

workflow源码解析:GoTask
关于go task 提供了另一种更简单的使用计算任务的方法,模仿go语言实现的go task。 使用go task来实计算任务无需定义输入与输出,所有数据通过函数参数传递。 与ThreadTask 区别 ThreadTask 是有模板,IN 和 OUT, ThreadTask 依赖…...

SpringMVC入门案例
引言 Spring MVC是一个基于MVC架构的Web框架,它的主要作用是帮助开发者构建Web应用程序。它提供了一个强大的模型驱动的开发方式,可以帮助开发者实现Web应用程序的各种功能,如请求处理、数据绑定、视图渲染、异常处理等。 开发步骤 1.创建we…...

Docker本地私有仓库搭建配置指导
一、说明 因内网主机需要拉取镜像进行Docker应用,因此需要一台带外主机作为内网私有仓库来提供内外其他docker业务主机使用。参考架构如下: 相关资源:加密、Distribution registry、Create and Configure Docker Registry、Registry部署、D…...

python 通过定时任务执行pytest case
这段Python代码使用了schedule库来安排一个任务,在每天的22:50时运行。这个任务执行一个命令来运行pytest,并生成一个报告。 代码开始时将job_done变量设为False,然后运行预定的任务。一旦任务完成,将job_done设置为True并跳出循…...
算法面试题:合并两个有序链表
描述:给定两个按非递减顺序排列的链表,合并两个链表,并将结果按非递减顺序排列。 例如: # 链表 1: 1 -> 2 -> 4 # 链表 2: 1 -> 3 -> 4合并后的链表应该是:1 -> 1 -> 2 -> 3 -> 4 -> 4 …...

LaWGPT安装和使用教程的复现版本【细节满满】
文章目录 前言一、下载和部署1.1 下载1.2 环境安装1.3 模型推理 总结 前言 LaWGPT 是一系列基于中文法律知识的开源大语言模型。该系列模型在通用中文基座模型(如 Chinese-LLaMA、ChatGLM等)的基础上扩充法律领域专有词表、大规模中文法律语料预训练&am…...

西门子博途用SCL语言写的入栈出栈
1.用户登录 #pragma code ("useadmin.dll") #include "PWRT_api.h" #pragma code() PWRTLogin(1) 2.用户退出 #pragma code ("useadmin.dll") #include "PWRT_api.h" #pragma code() PWRTLogout(); 3.画面跳转 SetPictureName("P…...

密码产品推介 | 沃通安全电子签章系统(ES-1)
产品介绍 沃通安全电子签章系统(ES-1)是一款基于密码技术、完全自主研发的商用密码产品,严格遵循国家密码管理局制定的相关标准,可为企业和个人提供安全、合规的电子签章功能服务。产品的主要用途是为各类文书、合同、表单等电子…...

蓝桥杯真题(Python)每日练Day1
说明:在CSP认证的基础上(可以看看本人CSP打卡系列的博客)备赛2024蓝桥杯(Python),本人专业:大数据与数据科学 因此对python要求熟练掌握,通过练习蓝桥杯既能熟悉语法又能锻炼算法和思…...

IDEA怎么用Devtools热部署
IDEA怎么用Devtools热部署 大家知道在项目开发过程中,有时候会改动代码逻辑或者修改数据结构,为了能使改动的代码生效,往往需要重启应用查看改变效果,这样会相当耗费时间。 重启应用其实就是重新编译生成新的Class文件࿰…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...