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

news2024/11/13 9:32:52

在这里插入图片描述

LRU的概念

LRU(Least Recently Used,最近最少使用)是一种常用的缓存淘汰策略,主要目的是在缓存空间有限的情况下,优先淘汰那些最长时间没有被访问的数据项。LRU 策略的核心思想是:

  1. 缓存空间有限:缓存只能存储一定数量的数据项。

  2. 淘汰最不常用的数据:当缓存满时,优先淘汰那些最近最少被访问的数据项。

  3. 访问记录:每次数据项被访问时,都会更新其访问记录,使得最近访问的数据项保留在缓存中。

  4. 数据替换:当需要加载新数据项到缓存中,但缓存已满时,会根据LRU策略淘汰一个或多个数据项,为新数据项腾出空间。

  5. 动态调整:随着数据访问模式的变化,LRU策略可以动态调整缓存中的数据项,以适应访问模式的变化。

在实现LRU缓存时,通常会使用数据结构如哈希表双向链表。哈希表用于快速定位缓存中的数据项,而双向链表则用于维护数据项的访问顺序。每次访问数据项时,都会将其移动到链表的头部,表示它是最近被访问的。当需要淘汰数据时,直接从链表的尾部开始淘汰即可。

LRU策略在许多场景中都非常有用,比如操作系统的页面置换、数据库的查询缓存、Web服务器的页面缓存等。它可以帮助系统更有效地利用有限的缓存资源,提高系统的整体性能。
在这里插入图片描述在这里插入图片描述
别急,我们先学实现LRU要用的哈希表双向链表

哈希表(unordered_map)

在C++中,unordered_map 是标准模板库(STL)中的一个关联容器,它基于哈希表的实现。它存储了键值对,允许通过键快速访问和修改值。unordered_map 提供了平均常数时间复杂度的访问、插入和删除操作。

主要特性

  1. 基于哈希表:通过哈希函数将键映射到存储位置,实现快速查找。
  2. 键不重复:每个键在容器中是唯一的。
  3. 无序存储:元素的存储顺序不依赖于插入顺序,因此迭代器的遍历顺序可能与插入顺序不同。

常用操作

  • 构造和初始化

    • 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):删除从 firstlast(不包括 last)范围内的所有元素。
    • erase(key):删除指定键的所有元素。
  • 大小和容量

    • size():返回容器中元素的数量。
    • empty():如果容器为空,返回 true
  • 迭代器

    • begin():返回指向容器开始的迭代器。
    • end():返回指向容器结束的迭代器。

示例代码

以下是使用 unordered_map 的一个简单示例:

#include <iostream>
#include <unordered_map>

int main() {
    // 创建一个 unordered_map,键为 int,值为 string
    unordered_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 的一些主要特性和常用操作:

特性

  1. 双向链表:每个元素都是链表中的一个节点,可以从前向后或从后向前遍历。
  2. 动态大小list 的大小可以根据需要动态变化,不需要预先定义大小。
  3. 插入和删除操作:可以在常数时间内在任意位置插入或删除元素,不需要像 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):删除从 firstlast(不包括 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,由于已被淘汰,返回 -1
    cout << "get(1) = " << cache.get(1) << endl; // 返回 -1
    // 访问键 3,输出其值
    cout << "get(3) = " << cache.get(3) << endl; // 返回 3

    // 插入键值对 (4, 4),由于缓存已满,键 2 被淘汰
    cache.put(4, 4);
    // 访问键 1,由于已被淘汰,返回 -1
    cout << "get(1) = " << cache.get(1) << endl; // 返回 -1
    // 访问键 3,输出其值
    cout << "get(3) = " << cache.get(3) << endl; // 返回 3
    // 访问键 2,由于已被淘汰,返回 -1
    cout << "get(2) = " << cache.get(2) << endl; // 返回 -1
    // 访问键 4,输出其值
    cout << "get(4) = " << cache.get(4) << endl; // 返回 4

    return 0;
}

这段代码首先定义了一个 LRUCache 类,该类使用 unordered_maplist 来实现 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了。收藏可以方便下次巩固哦!!!!
在这里插入图片描述

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

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

相关文章

航片转GIS数据自动化管线

近年来&#xff0c;计算机视觉领域的进步已显著改善了物体检测和分割任务。一种流行的方法是 YOLO&#xff08;You Only Look Once&#xff09;系列模型。YOLOv8 是 YOLO 架构的演进&#xff0c;兼具准确性和效率&#xff0c;是各种应用的绝佳选择&#xff0c;包括分割卫星航拍…

借助Python将txt文本内容导入到数据库

安装数据库并创建admin账号 #Create mariadb user CREATE USER admin% IDENTIFIED BY password; GRANT SELECT, INSERT, UPDATE, DELETE ON hosts_info.* TO admin%; FLUSH PRIVILEGES;创建库并创建数据表 #创建库 CREATE DATABASE hosts_info; #创建表 CREATE TABLE host_tm…

shell条件语句

一&#xff0c;条件测试 1 . test命令 测试表达式是否成立,若成立返回0,否则返回其他数值 1.1 格式 test 条件表达式 [ 条件表达式 ] 2 . 文件测试 2.1 格式 [ 操作符 文件或目录 ] 例 test -d /home/user 2.2 常用的测试操作符 -d:测试是否为目录(Directory)-e:测试目…

安装Ubuntu24.04服务器版本

Ubuntu系统安装 一.启动安装程序二.执行 Ubuntu Server 安装向导1.选择安装程序语言&#xff0c;通常选择「English」2.设置键盘布局&#xff0c;默认「English US」即可3.选择安装方式 三.配置网络1.按Tab键选择网络接口&#xff08;例如 ens160&#xff09;&#xff0c;然后按…

== 与 equals 的区别

概念 它的作用是判断两个对象的地址是不是相等&#xff0c;判断两个对象是不是同一个对象基本数据类型比较的是值是否相等引用数据类型比较的是内存地址是否相等 equals() 概念 它的作用也是判断两个对象是否相等。但它一般有两种使用情况&#xff1a;情况1&#xff1a;类没有…

重复图片查找:巧用Python和OpenCV进行图像哈希与汉明距离检测以从海量图片中找出重复图片

重复图片查找&#xff1a;巧用Python和OpenCV进行图像哈希与汉明距离检测以从海量图片中找出重复图片 1. 导言2. 环境准备3. 图像哈希&#xff08;pHash&#xff09;原理4. 汉明距离原理5. 代码实现导入必要的库图像哈希计算函数汉明距离计算函数查找重复图片函数示例使用 在处…

昇思25天学习打卡营第14天|计算机视觉

昇思25天学习打卡营第14天 文章目录 昇思25天学习打卡营第14天FCN图像语义分割语义分割模型简介网络特点数据处理数据预处理数据加载训练集可视化 网络构建网络流程 训练准备导入VGG-16部分预训练权重损失函数自定义评价指标 Metrics 模型训练模型评估模型推理总结引用 打卡记录…

Python机器学习入门:从理论到实践

文章目录 前言一、机器学习是什么&#xff1f;二、机器学习基本流程三、使用Python进行机器学习1.数据读取2.数据规范化3. 数据降维&#xff08;主成分分析&#xff09;4. 机器学习模型的选择5. 线性回归模型的实现6. 可视化结果 总结 前言 机器学习是人工智能的一个重要分支&…

RabbitMQ的学习和模拟实现|muduo库的介绍和使用

muduo库 项目仓库&#xff1a;https://github.com/ffengc/HareMQ muduo库 muduo库是什么快速上手搭建服务端快速上手搭建客户端上面搭建的服务端-客户端通信还有什么问题?muduo库中的protobuf基于muduo库中的protobuf协议实现一个服务器 muduo库是什么 Muduo由陈硕大佬开…

无人机之摄影构图指南

一、三分法构图 将画面分为三等分&#xff0c;水平线或地平线通常放在1/3处&#xff0c;使得画面看起来更加舒适。主体放在九宫格四个交点&#xff08;视觉中心&#xff09;上&#xff0c;突出视觉中心。 二、对称式构图 将画面左右或上下对等分割&#xff0c;形成呼应&…

HTML:lang属性作用

lang作用 用法常见语言代码优点示例结构效果说明分析HTML 基础结构导航栏内容部分总结 扩展 用法 HTML 文档级别: 在 <html> 标签上使用 lang 属性&#xff0c;指定整个文档的语言。 <!DOCTYPE html> <html lang"en"> <head><meta charse…

(C++) 文件读写基础

文章目录 &#x1f5c2;️前言&#x1f4c4;ref&#x1f4c4;访问标记&#x1f5c3;️流打开模式类型 &#x1f5c2;️Code&#x1f4c4;demo&#x1f4c4;分点讲解&#x1f5c3;️打开/关闭&#x1f5c3;️写&#x1f5c3;️读&#x1f5c3;️状态函数 &#x1f5c2;️END&…

javascript 的执行上下文与作用域

目录 1. 初步了解 上下文&#xff08;context&#xff09;2. 全局上下文(global context)3. 上下文栈 (context stack)4. 作用域链( scope chain)5. 作用域(scope)6. 作用域链增强 1. 初步了解 上下文&#xff08;context&#xff09; 上下文(context) 全称 执行上下文 (execut…

linux中RocketMQ安装(单机版)及springboot中的使用

文章目录 一、安装1.1、下载RocketMQ1.2、将下载包上传到linux中&#xff0c;然后解压1.3、修改runserver.sh的jvm参数大小&#xff08;根据自己服务器配置来修改&#xff09;1.4、启动mqnamesrv &#xff08;类似于注册中心&#xff09;1.5、修改runbroker.sh的jvm参数大小&am…

【Linux】进程信号 --- 信号处理

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

el-table列的显示与隐藏

需求&#xff1a;实现 表字段的显示与隐藏。效果图 代码实现 写在前面 首先 我部分字段有自定义的排序逻辑&#xff0c;和默认值或者 数据的计算 所以是不能简单的使用 v-for 循环column 。然后 我需要默认展示一部分字段&#xff0c;并且 当表无数据时 提示不能 显示隐藏 …

HTTP 缓存

缓存 web缓存是可以自动保存常见的文档副本的HTTP设备&#xff0c;当web请求抵达缓存时&#xff0c;如果本地有已经缓存的副本&#xff0c;就可以从本地存储设备而不是从原始服务器中提取这个文档。使用缓存有如下的优先。 缓存减少了冗余的数据传输缓存环节了网络瓶颈的问题…

学习大数据DAY21 Linux基本指令2

目录 思维导图 搜索查看查找类 find 从指定目录查找文件 head 与 tail 查看行 cat 查看内容 more 查看大内容 grep 过滤查找 history 查看已经执行过的历史命令 wc 统计文件 du 查看空间 管道符号 | 配合命令使用 上机练习 4 解压安装类 zip unzip 压缩解压 tar …

google 浏览器插件开发简单学习案例:TodoList

参考&#xff1a; google插件支持&#xff1a; https://blog.csdn.net/weixin_42357472/article/details/140412993 这里是把前面做的TodoList做成google插件&#xff0c;具体网页可以参考下面链接 TodoList网页&#xff1a; https://blog.csdn.net/weixin_42357472/article/de…

Web前端:HTML篇(一)

HTML简介&#xff1a; 超文本标记语言&#xff08;英语&#xff1a;HyperText Markup Language&#xff0c;简称&#xff1a;HTML&#xff09;是一种用于创建网页的标准标记语言。 您可以使用 HTML 来建立自己的 WEB 站点&#xff0c;HTML 运行在浏览器上&#xff0c;由浏览器…