LRU Cache替换算法

目录

1.什么是LRU Cache?

2.LRU Cache 的底层结构

3.LRU Cache的实现

LRUCache类中的接口总览

构造函数

get操作

 put操作

打印

4.LRU Cache的测试

5.LRU Cache相关OJ题

6.LRU Cache类代码附录


1.什么是LRU Cache?

首先我想解释一下什么是cache。所谓cache,其实就是一段缓冲区,位于运行速度相差较大的两种硬件之间, 用于协调两者数据传输速度差异的结构。

比如计算机的存储结构:

我们知道程序都是在磁盘上存储的,但是程序都要放在CPU上执行,CPU的执行速度非常快,而磁盘是外设,代码从外设搬到CPU上的速度又非常慢,此时,CPU大部分时间都在等待代码和数据的到来,这样无疑是对CPU的浪费;于是在外设和CPU之间添加Cache 缓存用于进行预加载;CPU每次不再向磁盘 “索要” 要代码和数据,而是从Cache上拿,CPU还在执行当前的代码和数据时,磁盘上的代码和数据又可以往Cache上加载,于是,便协调了CPU和磁盘之间的数据加载不平衡的问题。

旧的问题解决了,又产生了新的问题:

Cache的容量是有限的,因此当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容,那应该舍弃那部分内容呢?经过科学家的研究,认为舍弃最近最少使用(Least Recently Used过的数据是最合理的,也就是舍弃LRU数据。

上面说的都是硬件层的东西,但是需要通过软件层的算法来解决,也就是设计如何舍弃最近最少使用的数据,这便是LRU Cache 替换算法

2.LRU Cache算法的底层结构

要设计出一个LRU Cache替换算法不难,但是要设计出一个高效的LRU Cache替换算法有难度,高效体现在任意操作的时间复杂度都是O(1)

说明一下:我们设计的LRU Cache中存储的数据为键值对的形式。

那LRU Cache替换算法都有哪些操作呢?其实也就两个主要操作。

  • 一个操作是往Cache中放数据(put操作)。要求:如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity (Cache的容量),则应该 逐出 最久未使用的关键字。
  • 一个操作是把数据从Cache中拿出来(get操作)。要求:如果关键字 key 存在于缓存中,则返回关键字的值,否则返回默认值并提示。

如何使这两个操作的时间复杂度都达到O(1)呢?此时,底层数据结构的选择尤为重要。

  • 无论是get还是put操作,都需要快速根据关键字的值查找一个元素是否存在于 Cache 中,我们知道,查找一个元素时间复杂度为O(1)的数据结构是哈希表,因此我们可以选择STL中的unordered_map作为其底层结构。
  • 但是这还不够,我们需要频繁的进行数据的增加和删除操作,并且要求数据的增加和删除操作的效率都是O(1)。因此,我们还可以借助STL中的 list(带头双向循环链表)来存储数据。
  • 同时,因为我们还需要保证Cache满了之后,替换的是最近最少使用的数据,我们可以让链表的末尾存放最近最少使用的数据,每次替换的时候删除末尾的数据即可。为了实现这一点,我们只需要将每次访问过的数据提取到 list 的头部即可,尾部自然而然就是最近最少使用的数据。

LRU Cache的底层存储示意图:

3.LRU Cache算法的实现

LRUCache类中的接口总览

说明一下:我们将LRUCache类设计为模版类,以便适应各种数据类型。

template<class Key, class Val>
class LRUCache
{
public:// 构造函数LRUCache(int capacity):_capacity(capacity){}// 通过key获取对应的valVal get(Key key);// 往Cache中插入<key,value>void put(Key key, Val value);// 输出list中的结点,便于调试分析void print();private:// 对list的迭代器类型进行重命名using LtIte = typename std::list<std::pair<Key, Val>>::iterator;// 保证查找的效率是O(1)std::unordered_map<Key, LtIte> _hashMap;// 保证插入删除数据的效率是O(1)std::list<std::pair<Key, Val>> _LRU_list;// Cache的容量size_t _capacity;
};

构造函数

直接指定LRUCache中的容量即可。

// 构造函数
LRUCache(int capacity):_capacity(capacity)
{}

get操作

  • 首先通过哈希表判断key是否存在,如果存在则返回对应的value,并且将访问过的元素提取到list的开头。
  • 如果不存在,返回该类型通过默认构造函数构造出的对象即可,并提示该元素不存在。

代码如下所示:

// 通过key获取对应的val
Val get(Key key)
{auto ret = _hashMap.find(key);if (ret != _hashMap.end())   // 获取val的同时,更改key所在结点的位置{// 获取元素在list中的结点LtIte it = ret->second;// 使用list的splice接口将当前访问的结点转移到链表的开头_LRU_list.splice(_LRU_list.begin(), _LRU_list, it);// 返回list结点中valreturn it->second;}std::cout << "该元素不存在" << std::endl;return Val();
}
  • 注意:这里将访问过的结点提取到链表的开头也可以通过erase+push_front来实现,但是需要更新迭代器,防止迭代器失效的问题。因此,我们使用操作更加简单的list的splice接口。

 put操作

  • put数据的时候,需要判断该数据的key值是否存在,如果存在则更新对应的value,如果不存在则插入,并且两种情况都视为访问过当前结点,需要将访问过的结点转移到list的开头位置。
  • 在该数据不存在的情况下,需要判断Cache中数据是否满了,满了就要删除list末尾的数据(LRU数据)。
// 往Cache中插入<key,value>
void put(Key key, Val value)
{auto ret = _hashMap.find(key);if (ret == _hashMap.end()) // key不存在,需要插入<key,value>{// 如果满了,就要删除LRU的数据if (_capacity == _hashMap.size()){// 从list中获取尾部的数据std::pair<Key, Val> back_data = _LRU_list.back();// 删除哈希表中对应的数据_hashMap.erase(back_data.first);// 删除list中对应的数据_LRU_list.pop_back();}// 头插新来的<key,value>结点_LRU_list.push_front(std::make_pair(key, value));// 将新数据添加到哈希表中_hashMap[key] = _LRU_list.begin();}else  // list中存在key,需要更新key对应的value{// 获取list中结点的迭代器LtIte it = ret->second;// 修改结点中的valueit->second = value;// 将访问过的结点转移到链表的头部_LRU_list.splice(_LRU_list.begin(), _LRU_list, it);}
}

打印

这个接口不是必需的,只是为了检测、调试LRUCache类。

// 打印list中的数据
void print()
{for (auto e : _LRU_list){std::cout << e.first << ":" << e.second << std::endl;}
}

4.LRU Cache算法的测试

测试代码:

#include <string>
#include "LRUCache.h"int main()
{LRUCache<std::string, std::string> lc(5);lc.put("book", "书");lc.put("string", "字符串");lc.put("water", "水");lc.put("computer", "计算机");lc.put("glass", "玻璃");lc.print();std::cout << std::endl;lc.get("water");lc.print();std::cout << std::endl;lc.put("book", "预定");lc.print();std::cout << std::endl;return 0;
}

运行结果:

5.LRU Cache算法相关OJ题

题目链接:

146. LRU 缓存 - 力扣(LeetCode)146. LRU 缓存 - 请你设计并实现一个满足  LRU (最近最少使用) 缓存 [https://baike.baidu.com/item/LRU] 约束的数据结构。实现 LRUCache 类: * LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 * int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。 * void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。 示例:输入["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"][[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]输出[null, null, null, 1, null, -1, null, -1, 3, 4]解释LRUCache lRUCache = new LRUCache(2);lRUCache.put(1, 1); // 缓存是 {1=1}lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}lRUCache.get(1); // 返回 1lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}lRUCache.get(2); // 返回 -1 (未找到)lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}lRUCache.get(1); // 返回 -1 (未找到)lRUCache.get(3); // 返回 3lRUCache.get(4); // 返回 4 提示: * 1 <= capacity <= 3000 * 0 <= key <= 10000 * 0 <= value <= 105 * 最多调用 2 * 105 次 get 和 puticon-default.png?t=O83Ahttps://leetcode.cn/problems/lru-cache/题目描述:

请你设计并实现一个满足  LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

示例:

输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

解题思路:

参考本篇博客即可。

运行代码:

class LRUCache {// 对list的迭代器类型进行重命名using LtIte = list<pair<int, int>>::iterator;
public:// 构造函数LRUCache(int capacity):_capacity(capacity){}// 通过key获取对应的valint get(int key){auto ret = _hashMap.find(key);if (ret != _hashMap.end())   // 获取val的同时,更改key所在结点的位置{// 获取元素在list中的结点LtIte it = ret->second;// 使用list的splice接口将当前访问的结点转移到链表的开头_LRU_list.splice(_LRU_list.begin(), _LRU_list, it);// 返回list结点中valreturn it->second;}std::cout << "该元素不存在" << std::endl;return -1;}// 往Cache中插入<key,value>void put(int key, int value){auto ret = _hashMap.find(key);if (ret == _hashMap.end()) // key不存在,需要插入<key,value>{// 如果满了,就要删除LRU的数据if (_capacity == _hashMap.size()){// 从list中获取尾部的数据std::pair<int, int> back_data = _LRU_list.back();// 删除哈希表中对应的数据_hashMap.erase(back_data.first);// 删除list中对应的数据_LRU_list.pop_back();}// 头插新来的<key,value>结点_LRU_list.push_front(std::make_pair(key, value));// 将新数据添加到哈希表中_hashMap[key] = _LRU_list.begin();}else  // list中存在key,需要更新key对应的value{// 获取list中结点的迭代器LtIte it = ret->second;// 修改结点中的valueit->second = value;// 将访问过的结点转移到链表的头部_LRU_list.splice(_LRU_list.begin(), _LRU_list, it);}}private:unordered_map<int, LtIte> _hashMap;list<pair<int, int>> _LRU_list;size_t _capacity;
};

运行结果:

6.LRU Cache类代码附录

#include <iostream>
#include <unordered_map>
#include <list>template<class Key, class Val>
class LRUCache
{
public:// 构造函数LRUCache(int capacity):_capacity(capacity){}// 通过key获取对应的valVal get(Key key){auto ret = _hashMap.find(key);if (ret != _hashMap.end())   // 获取val的同时,更改key所在结点的位置{// 获取元素在list中的结点LtIte it = ret->second;// 使用list的splice接口将当前访问的结点转移到链表的开头_LRU_list.splice(_LRU_list.begin(), _LRU_list, it);// 返回list结点中valreturn it->second;}std::cout << "该元素不存在" << std::endl;return Val();}// 往Cache中插入<key,value>void put(Key key, Val value){auto ret = _hashMap.find(key);if (ret == _hashMap.end()) // key不存在,需要插入<key,value>{// 如果满了,就要删除LRU的数据if (_capacity == _hashMap.size()){// 从list中获取尾部的数据std::pair<Key, Val> back_data = _LRU_list.back();// 删除哈希表中对应的数据_hashMap.erase(back_data.first);// 删除list中对应的数据_LRU_list.pop_back();}// 头插新来的<key,value>结点_LRU_list.push_front(std::make_pair(key, value));// 将新数据添加到哈希表中_hashMap[key] = _LRU_list.begin();}else  // list中存在key,需要更新key对应的value{// 获取list中结点的迭代器LtIte it = ret->second;// 修改结点中的valueit->second = value;// 将访问过的结点转移到链表的头部_LRU_list.splice(_LRU_list.begin(), _LRU_list, it);}}// 打印list中的数据void print(){for (auto e : _LRU_list){std::cout << e.first << ":" << e.second << std::endl;}}private:// 对list的迭代器类型进行重命名using LtIte = typename std::list<std::pair<Key, Val>>::iterator;// 保证查找的效率是O(1)std::unordered_map<Key, LtIte> _hashMap;// 保证插入删除数据的效率是O(1)std::list<std::pair<Key, Val>> _LRU_list;// Cache的容量size_t _capacity;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/63206.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数字孪生与大型模型强强联合,共塑工业制造崭新前景

随着新一代信息技术与实体经济的加速融合&#xff0c;工业领域的数字化、智能化转型趋势愈发显著&#xff0c;孕育出一系列制造业数字化转型的新模式与新业态。在此背景下&#xff0c;数字孪生技术作为关键支撑力量&#xff0c;正在全球范围内迅速崛起并得到广泛应用&#xff0…

【笔记2-5】ESP32:freertos消息队列

主要参考b站宸芯IOT老师的视频&#xff0c;记录自己的笔记&#xff0c;老师讲的主要是linux环境&#xff0c;但配置过程实在太多问题&#xff0c;就直接用windows环境了&#xff0c;老师也有讲一些windows的操作&#xff0c;只要代码会写&#xff0c;操作都还好&#xff0c;开发…

Qt Chart 模块化封装曲线图

一 版本说明 二 完成示例 此文章包含:曲线轴设置,曲线切换,单条曲线显示,坐标轴。。。 三 曲线图UI创建 在UI界面拖放一个QWidget,然后在 Widget里面放一个 graphicsView 四 代码介绍 1 头文件 #include <QString> #include <QTimer> #include <QMessa…

a7678 食品添加剂健康小助手系统微信小程序的的设计与实现 后台php+mysql+layui+thinkphp 源码 配置 文档 全套资料

食品添加剂健康小助手 1.摘要2.开发目的和意义3.系统功能设计4.系统界面截图5.源码获取 1.摘要 食品添加剂健康小助手系统是一个能够帮助消费者更好地了解食品添加剂相关信息的智能系统。在现代食品生产过程中&#xff0c;许多食品添加剂被广泛使用&#xff0c;以提高食品的质…

HTTP(超文本传输协议)

HTTP是万维网通信的基础构成&#xff0c;是一个简单的请求相应协议&#xff0c;基于TCP之上80号端口 通信原理 DNS解析 将域名甩个DNS服务器解析&#xff0c;将域名化为IP访问 建立TCP连接 如图&#xff0c;客户端先发送一个sys置位seq为x&#xff08;任意值&#xff09;的…

如何设置合理的爬取频率避免被网站封锁?

要合理设置爬取频率以避免被网站封锁&#xff0c;可以采取以下几种策略&#xff1a; 遵守robots.txt规范&#xff1a;确保爬虫程序遵守目标网站的robots.txt文件中定义的爬取规则&#xff0c;避免爬取被网站禁止的内容。 设置请求头信息&#xff1a;在爬取时&#xff0c;设置合…

SpringBoot的validation参数校验

文章目录 前言一、引入validation 依赖二、validation中的注解说明 &#xff08;1&#xff09;Validated&#xff08;2&#xff09;Valid&#xff08;3&#xff09;NotNull&#xff08;4&#xff09;NotBlank&#xff08;5&#xff09;NotEmpty&#xff08;6&#xff09;Patte…

SSH克隆github项目

1、生成密钥 ssh-keygen -t rsa -C "你的邮箱xxx.com" 全程回车即可&#xff08;不用输入ras文件名及密码&#xff09;、为了方便下面的公钥查看 2、配置公钥 查看公钥内容 cat c:\Users\xxx\.ssh\id_rsa.pub(修改为自己的路径及名字) 将公钥内容复制并粘贴至…

CASAIM与中国航天携手合作,CASAIM IS全自动化光学测量系统交付中国航天山西工厂,助力航空航天零部件全自动化3D测量

近日&#xff0c;CASAIM与中国航天达成全自动化光学测量技术合作&#xff0c;并将CASAIM IS全自动化光学测量系统交付给中国航天科技集团山西工厂&#xff0c;这一合作标志着双方在智能制造和精密测量领域迈出了重要一步。 中国航天科技&#xff0c;是在中国战略高技术领域拥有…

基于Springboot+Vue的电子博物馆系统

基于SpringbootVue的电子博物馆系统 前言&#xff1a;随着信息技术的不断发展&#xff0c;传统博物馆的参观方式逐渐向数字化、在线化转型。电子博物馆作为这一转型的重要组成部分&#xff0c;能够通过信息化手段为用户提供更丰富、更便捷的博物馆参观体验。本文基于Spring Boo…

在Docker中部署禅道,亲测可用

1、确保centos中已安装docker docker -v 2、启动docker systemctl start docker 3、可设置docker开机启动 systemctl enable docker.service 4、获取最新版禅道开源版镜像 docker pull idoop/zentao 5、运行镜像生成禅道容器【创建 /data/www /data/data 目录】 doc…

vitepress组件库文档项目 markdown语法大全(修正版)

#上次总结的 有些语法是用在markdown文档中的 使用到vitepress项目中有些语法可能有出入 于是我再总结一版 vitepress项目中的markdown语法大全 在阅读本章节之前&#xff0c;请确保你已经对 Markdown 有所了解。如果你还不了解 Markdown &#xff0c;请先学习一些Markdown 教…

Blender导入下载好的fbx模型像的骨骼像针戳/像刺猬

为什么我下载下来的骨骼模型和我自己绑定的模型骨骼朝向完全不一样 左边是下载的模型 右边是我自己绑定的模型 左边的模型刚刚感觉都是像针一样往外戳的&#xff0c;像刺猬一样那种。 解决方法勾选自动骨骼坐标系

ASP.NET CORE API 解决跨域问题

环境 vs2022 .net 8 创建ASP.net Core API项目 配置跨域 编写ApiController 启动项目 得到服务器运行的 地址 在Hbuiler中创建web项目&#xff0c;编写代码 【运行】-【运行到浏览器】-选择一个浏览器,查看结果 正常显示 问题 如果允许所有源访问&#xff0c;有安全风险方…

【AI系统】MobileFormer

MobileFormer 在本文中&#xff0c;将介绍一种新的网络-MobileFormer&#xff0c;它实现了 Transformer 全局特征与 CNN 局部特征的融合&#xff0c;在较低的成本内&#xff0c;创造一个高效的网络。通过本节&#xff0c;让大家去了解如何将 CNN 与 Transformer 更好的结合起来…

决策树:ID3、C4.5和CART特征选择方式

1 前言 该文章主要目的是记录ID3、C4.5和CART特征选择方式&#xff0c;这里只对决策树进行简单介绍。 决策树&#xff08;Decision Tree&#xff09;算法是一种有监督学习算法&#xff0c;它利用分类的思想&#xff0c;根据数据的特征构建数学模型&#xff0c;从而达到数据的筛…

【3D AIGC】Img-to-3D、Text-to-3D、稀疏重建(2024年文章汇总)

文章目录 1. Wonderworld&#xff1a;拓展图片边界&#xff0c;生成3D场景2. 3DTopia-XL&#xff1a;扩散模型辅助生成3. 3DGS-Enhancer: 通过视图一致2D Diffusion&#xff0c;提升无界3D Gaussian Splatting (NlPs2024 Spotlight)4. L3DG&#xff1a;Latent 3D Gaussian Diff…

三款电容麦的对比

纸面参数 第一款麦克风 灵敏度: -36 dB 2 dB&#xff08;0 dB1V/Pa at 1 kHz&#xff09; 灵敏度较低&#xff0c;需要更高的增益来拾取同样的音量。频率响应: 40 Hz - 18 kHz 响应范围较窄&#xff0c;尤其在高频区域。等效噪音级: ≤18 dB&#xff08;A计权&#xff09; 噪…

运行 GreatSQL 时为什么要求关闭透明大页

在大部分运维规范中&#xff0c;一般都会要求在运行 GreatSQL/MySQL 的环境中要关闭透明大页&#xff0c;那么到底什么是透明大页&#xff0c;为什么要关闭&#xff0c;打开有什么风险吗&#xff1f; 在此之前&#xff0c;我也是有点懵的&#xff0c;本文试着回答这个疑问&…

JUnit介绍:单元测试

1、什么是单元测试 单元测试是针对最小的功能单元编写测试代码&#xff08;Java 程序最小的功能单元是方法&#xff09;单元测试就是针对单个Java方法的测试。 2、为什么要使用单元测试 确保单个方法运行正常&#xff1b; 如果修改了代码&#xff0c;只需要确保其对应的单元…