LRU算法学习笔记:实现以及应用

LRU算法(C++实现) 一、LRU算法简介 对于web开发而言,缓存必不可少,也是提高性能最常用的方式。无论是浏览器缓存,还是服务端的缓存(通过memcached或者redis等内存数据库)。缓存不仅可以加速用户的访问,同时也可以降低服务器的负载和压力。那么,了解常见的缓存淘汰算法的策略和原理就显得特别重要。 常见的缓存算法 LRU (Least recently used) 最近最少使用,如果数据最近被访问过,那... 阅读详情

一、LRU算法的实现

其实很久以前就已经学过了LRU算法,在秋招面试之余整理成文章,以便面试的时候说得条理清晰一点。

所谓LRU(Least Recently Used)就是最近最少使用算法。其核心的思想就是根据每个节点的最后一次访问事件来对每个结点进行排序。最近一次使用的排在队头,依次类推。

如果实现这样的一个链表,一般需要实现两个功能:

  1. 快速插入元素(这个元素包含key,value两个部分)
  2. 快速访问元素(根据key快速拿到value)

此外,这个链表随着不断的插入新的元素,肯定不断变长。因此对于这个链表来说,需要一个容量上限。超过了这个容量的上限,那么就需要把链表尾部(距离现在最久一次访问的结点)进行删除。

所以,实现这个链表,还隐藏了第三个功能:

  1. 快速删除元素

因此:

  1. 从快速删除、插入的角度来看的话,需要一个链表来维护结点的时序关系。
  2. 从快速根据元素的key来拿到value来看的话,需要一个哈希表来维护key和value的关系。

根据这种思路,我们可以很快的想到:

维护一个链表,链表中的结点放value,然后维护一个哈希表,可以根据key快速的拿到结点,然后取出里面的key。

实际上,还需要考虑一些其他的细节:

  • 链表是单向还是双向?
  • 结点这样设计真的可以吗?

回答:

  • 链表需要双向。我们思考什么时候会删除一个元素:
    1. 链表满了,我们需要删除尾部的结点。
    2. 访问了链表中间的结点,需要把这个结点删除,然后插到链表头。

在上面的这两种情况下,删除一个结点cur需要将这个结点的前一个结点的next指针指向cur的下一个结点。因此,需要知道cur的前一个结点是谁。

  • 结点需要同时维护key和value,不能只有value。 假设现在因为插入一个结点到链表里面,导致链表满了,需要删除尾部的结点。那么尾部结点是很容易删除,但是哈希表中维护了这个结点的信息也要被删除。结点里面只有value,怎么知道哈希表里面删除谁呢?

有了这些思考,我们就可以实现一个LRU结构了。

二、LRU算法的实现(C++)

根据上面的分析,我们实现的LRU数据结构图如下:

在这里插入图片描述

  • unordered_map用于快速根据key找到结点

  • 链表中的结点包括四个变量:

    1. prev_:指向上一个结点
    2. next_:指向下一个结点
    3. key_:关键字
    4. value_:存储元素

下面是具体实现:

class LRUCache {
private:
    struct Node {
        Node(int key = -1, int val = 0, Node* prev = nullptr, Node* next = nullptr) : key_(key), val_(val), prev_(prev), next_(next) {} 
        Node* prev_;
        Node* next_;
        int key_;
        int val_;
    };

public:
    LRUCache(int capacity) : cap_(capacity), size_(0) {
        head_ = new Node();
        tail_ = new Node();
        
        head_->next_ = tail_;
        tail_->prev_ = head_;
    }
    
    int get(int key) {
        auto it = key2Node_.find(key);
        if (it == key2Node_.end()) return -1;

        Node* node = it->second;
        int res = node->val_;

        //1. 从链表中拆下这个节点
        deleteNode(node);
        //2. 添加到链表头
        addToHead(node);

        return res;
    }

    void deleteNode(Node* node) {
        node->prev_->next_ = node->next_;
        node->next_->prev_ = node->prev_;
    }

    void addToHead(Node* node) {
        node->next_ = head_->next_;
        head_->next_->prev_ = node;

        node->prev_ = head_;
        head_->next_ = node;
    }
    
    void put(int key, int value) {
        auto it = key2Node_.find(key);
        if (it == key2Node_.end()) {
            // 1. 创建节点
            Node* addNode = new Node(key, value);
            key2Node_[key] = addNode;
            // 2. 插到队头
            addToHead(addNode);
            // 3. 判断是否溢出
            ++size_;
            if (size_ > cap_) {
                key2Node_.erase(tail_->prev_->key_);
                deleteNode(tail_->prev_);
                --size_;
            }
        } else {
            // 1. 更改节点的值
            Node* node = it->second;
            node->val_ = value;
            // 2. 删除节点
            deleteNode(node);
            // 3. 插到队头
            addToHead(node);
        }
    }

private:

    unordered_map<int, Node*> key2Node_;
    Node* head_ = nullptr;
    Node* tail_ = nullptr;

    int size_ = 0;
    int cap_ = 0;
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

三、LRU的实际应用

LRU算法实际上在MySQL上和Redis上都有应用,不过都是LRU算法的变种。

1. 在MySQL上的应用

在MySQL中,LRU算法主要用于缓存的淘汰。所谓缓存,就是在每次查询的时候把结果用链表形式串起来,下次访问相同的内容时,就可以直接从链表中查询了。

这条链表,就是LRU中的链表,链表的“结点”,就是磁盘中一页的数据,也就是16K的数据。但是,使用这种原始的LRU算法确是不妥的,原因有两个:全表扫描 和 磁盘预读。

全表扫描

对于MySQL来说,有些SQL语句的查询需要遍历整个数据库,在遍历的过程中,MySQL在磁盘中的每一页数据都频繁的在LRU链表中移动。

更糟糕的是,遍历的数据太多,而且遍历完可能完全就没有用了,但是数据却把原来LRU上频繁使用的数据给挤掉了。那么,在下次一大批热点数据的查询过来的时候,数据库(假设没有Redis)可能就直接崩溃了。

磁盘预读

学过操作系统的同学应该知道,操作系统在访问磁盘的数据的时候,也不是仅仅加载所需要的数据到内存中,而是进行了一部分的预读。因为根据程序的局部性原理,接下来的数据很可能也会被访问到。对于MySQL也是如此,访问了磁盘的内容,MySQL就会预读一部分的数据

如果在MySQL中,访问一个区的数据超过56页,那么MySQL就会把下个区的全部内容都加入到MySQL的缓存中。

如果在MySQL中,缓存已经有了某个区的13页的内容,那么这个区的其余内容也会被加载到MySQL的缓存中。

由于这两个缺点,MySQL做了自己的LRU算法的改进。改进方式是维护了两条LRU链表,也称为冷热LRU链表。

冷LRU维护的都是冷数据。在MySQL中,从磁盘刚读进来的数据都放在冷LRU链表中。

如果冷LRU的数据在1秒后又被访问了,才会从冷LRU链表转移到热LRU链表中。

这样就完美解决了磁盘预读和全表扫描的问题了。

2. 在Redis上的应用

Redis是专门来作为数据库缓存的内存型数据库,也就是所有的数据都放在了内存中。因此,和MySQL一样,Redis需要对一些数据进行淘汰。

从Redis数据库设计者的角度来看,也不将LRU原始算法作为数据库内存的淘汰策略,其主要考虑了两个问题:

  1. 维护链表需要时间
  2. 大量数据的访问需要频繁的移动链表,又是十分耗时的操作。

基于以上两个问题,Redis使用的LRU算法是一种近似LRU算法,其实现十分简单:

在数据对象的结构体上增加一个时间字段,记录了最后一次使用的时间。当需要进行淘汰的时候,直接随机选出5个数据对象,然后删除最久未使用的那个数据。

因此,在Redis中,不要维护链表、也没有链表结点被访问时的频繁移动,速度快很多。
但是,不像MySQL,Redis的这种策略是没办法解决缓存污染的问题的。也就是一次性访问很多数据,只访问了一次但是淘汰的时候被考虑到的优先级却低很多。

因此,在Redis中,引入了LFU算法来实现内存的淘汰策略。(挖坑,后面有时间补上)


参考文献:

  1. MySQL是如何对LRU算法进行优化的?又该如何对MySQL进行调优?
  2. Redis 过期删除策略和内存淘汰策略有什么区别?
C++实现LRU算法 LRU算法 LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,是为虚拟页式存储管理服务的。LRU算法的提出,是基于这样一个事实:已经很久没有使用的页面很可能在未来较长的一段时间内不会被用到。因此,我们只需要在每次调换时,找到最近最久未使用的那个页面调出内存。这就是LRU算法的全部内容。 实现 使用双向链表和哈希表实现LRU算法 双向链表用于保存key-va... 阅读详情

相关推荐

LinkedList实现基于LRU算法的缓存

学过操作系统的人都知道LRU页面切换算法,其实这个算法不仅仅只是能在页面切换中应用到,在缓存中也有很实际的应用。最典型的实现方式是采用LinkedHashMap来实现这个缓存,大家可以在Java源码里面看到这个类的作者关于这个的描述,不过全是英文,但是却明确提到过。 下面废话不多说,直接展示我自己关于这个算法实现的代码吧,亲测通过: 核心算法代码: package hk.inso.www

心灵港 3954

LRU算法 原理及实现

LRU原理 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。 最常见的实现是使用一个链表保存缓存数据,详细算法实现如下 新数据插入到链表头部; 每当缓存命中(即缓存数据被访问),则将数据移到链表头部; 当链表满的时候,将链表尾部的数据丢弃。 【命中率】 当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。.

qq:489366879 1192

Windows Server 2019/2022 RDS 授权实战:3步激活许可证服务器,解决120天宽限期问题

本文详细介绍了Windows Server 2019/2022 RDS授权实战,提供3步激活许可证服务器的完整解决方案,解决120天宽限期问题。涵盖远程桌面授权服务器配置、许可证激活方法及组策略设置,确保企业合规使用远程桌面服务。

weixin_32705179的博客 967

二分查找 跳表 LRU缓存 贪心算法 霍夫曼编码 分治算法 回溯算法 动态规划

二分查找: 只能是有序的数组,而且应该用在插入删除不频繁的场景中。 public static int commonBinarySearch(int[] arr,int key){ int low = 0; int high = arr.length - 1; if(key &amp;amp;lt; arr[low] || key &amp;amp;gt; arr[high...

weixin_40805537的博客 525

CAS5和CAS6自定义异常提示消息

CAS自定义异常提示消息

weixin_42949219的博客 979

别再手动敲代码了!用STM32CubeMX+FreeRTOS图形化配置,5分钟搞定多任务通信框架

本文详细介绍了如何利用STM32CubeMX和FreeRTOS快速搭建多任务通信框架,大幅提升嵌入式开发效率。通过图形化配置工具,开发者可在5分钟内完成任务调度、时钟配置和内核对象生成,特别适合物联网设备开发。文章还提供了代码优化和调试技巧,帮助开发者轻松应对复杂应用场景。

weixin_30363285的博客 291

LRU 实现原理

前言 我们常用缓存提升数据查询速度,由于缓存容量有限,当缓存容量到达上限,就需要删除部分数据挪出空间,这样新数据才可以添加进来。缓存数据不能随机删除,一般情况下我们需要根据某种算法删除缓存数据。常用淘汰算法有 LRU,LFU,FIFO,这篇文章我们聊聊 LRU 算法。 LRU 简介 LRU 是 Least Recently Used 的缩写,这种算法认为最近使用的数据是热门数据,下一次很大概率将会再次被使用。而最近很少被使用的数据,很大概率下一次不再用到。当缓存容量的满时候,优先淘汰最近很少使用的数据。 假

稻草人技术博客 2683

鲸鱼优化算法(WOA)与门控循环单元(GRU)结合的预测模型(WOA-GRU)及其Python和MATLAB实现

Python和MATLAB的实现均突出模型结构和优化过程。2. **全局搜索与局部搜索结合**:WOA在寻优时,既有全局搜索的能力,又具备局部精准搜索的能力,能有效避免陷入局部最优解。2. **简化结构**:与长短期记忆(LSTM)相比,GRU的结构更为简化,避免了多层细胞结构,提高了计算效率。4. **使用WOA进行优化**:通过WOA迭代优化GRU模型中的参数,如学习率、批量大小和GRU单元的权重。3. **良好的表现**:GRU在多个时序数据序列的预测任务中表现优异,特别是在较短的数据序列中。

qq_45441438的博客 1323

LRU算法的详细介绍与实现

LRU算法的实现

weixin_45746505的博客 5571

ubuntu20.04升级OpenSSH

业务需求,需要将服务器的OpenSSH版本从8.2升级到最新版本(9.9),由于apt能提供的最新版本就是8.2,所以需要采用源码安装的方式。

xiamu66的博客 1524

中小企业办公网络配置综合实验华为设备(NAT+策略路由+OSPF+VLAN)

组网需求: 1、PC1、2、3、4均可以访问internet的lo0:8.8.8.8 2、PC1、PC4通过电信ISP访问internet;PC2、PC3通过联通ISP访问internet; 需求分析: 1、internet路由器模拟公网,8.8.8.8模拟公网IP;电信ISP、联通ISP路由器模拟电信和联通运营商;出口路由为企业出口路由器;这四个路由器直连链路之间运行ospf,配置动态路由,实现互通; 2、出口路由与三层交换直连链路运行ospf,动态同步路由信息; 3、出口路由器内网端口配..

A soul tortured by desire 7125

LRU算法介绍、优化、使用场景(Redis、MySQL、Memcached)

是的

Kindle_code的专栏 3521

ASPICE标准快速掌握「1.1. 简介」

ASPICE是汽车软件过程改进能力模型(Automotive SPICE,Automotive - Software Process Improvement and Capability dEtermination)的缩写,是一种针对汽车软件开发过程的国际标准。该标准旨在帮助汽车制造商和供应商改进其软件开发过程,以确保其软件产品的质量和安全性。需要做什么为什么要做技术上的依赖性是什么。

雪云飞星的博客 2292

【算法】LRU缓存算法的C++实现

LRU缓存算法是一种淘汰算法,也是一道经典的面试题,本文对LRU缓存算法的C++实现做了讲解。

Conner7的博客 6653

在Simulink中实现一个基于SiC MOSFET的高效率电机驱动系统

本文介绍了在Simulink中构建基于SiC MOSFET的高效率电机驱动系统的详细流程。系统由直流电源模块、SiC MOSFET逆变器模块、PMSM电机模型、控制策略模块(包括矢量控制和PWM生成)以及反馈测量模块组成。文章逐步指导了从模型创建到参数设置的完整过程,包括各模块的具体实现方法。通过仿真可以评估系统在不同负载条件下的响应速度、效率、功率密度等性能指标。该建模方法为电机驱动系统的设计和优化提供了有效工具,同时建议根据实际硬件规格和工程需求进行参数调整和定制化开发。

xiaoheshang_123的博客 248

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

LRU算法 java实现

定义:LRU全称Least Recently Used,也就是最近最少使用的意思,是一种内存管理算法,最早应用于Linux系统。 LRU算法基于一种假设:长期不使用的数据,在未来被用到的几率也不大。因此当数据所占内存达到一个阈值是,可以选择移除掉最近最少被使用的数据来保持内存的高效使用,这是基于时间局部性原理的一个假设。LRU算法主要用于缓存算法,在节省资源的情况下提高数据访问效率。 LRU使...

leo_weile的博客 7968

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

LRU算法及Java实现

LRU算法介绍 LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,为虚拟页式存储管理服务。LRU算法的提出,是基于这样一个事实:在前面几条指令中使用频繁的页面很可能在后面的几条指令中频繁使用。反过来说,已经很久没有使用的页面很可能在未来较长的一段时间内不会被用到。这个,就是著名的局部性原理。此外,LRU算法也经常被用作缓存淘汰策略。本文将基于LRU算法的思想...

To be a great coder 1万+

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

LRU算法(java实现)

Java实现LRU算法算法导入一、虚拟存储器的基本工作情况二、页面置换算法算法分析算法设计一、实现栈:二、实现主类:三、盲目分析四、敲代码算法源码及运行结果算法总结 算法导入        哈喽小伙伴们,英俊的我又回来了哈哈哈。今天给大家带来的是操作系统中的页面置换算法LRU算法实现。    &nb...

我家有只小金毛的博客 5882

LRU算法及应用

文章目录一 什么是LRU算法二 手工实现LRU2.1 简单链表实现2.2 解决命中率和缓存污染问题2.3 解决执行效率问题三 Redis如何利用LRU3.1 缓存淘汰策略3.2 LRU实现3.2.1 近似算法3.2.2 近似算法优化3.2.3 LFU算法四 结语 一 什么是LRU算法   LRU的全名为Least Recently Used,意指最近少用,这是一种非常经典的算法,应用范围非常的广,...

o983950935的博客 2614

缓存置换算法 - LRU算法

LRU算法 1 原理 对于在内存中并且不被使用的数据块就是LRU,这类数据需要从内存中删除,以腾出空间来存储常用的数据。 LRU算法(Least Recently Used,最近最少使用),是内存管理的一种页面置换算法,就是用来删除内存中不被使用的数据,腾出空间来把常用的数据存进去。 LRU算法的实现原理:把所有的缓存数据存入链表中,新插入的或被访问的数据存入链表头,如果链表满了,就把尾部...

u010482601的专栏 2629

Redis的LRU算法实现源码解析

Redis的LRU算法实现源码解析 LRU 算法的基本原理 首先,我们需要理解 LRU 算法的基本原理。LRU 算法就是指最近最少使用(Least Recently Used,LRU)算法,这是一个经典的缓存算法。 从基本原理上来说,LRU 算法会使用一个链表来维护缓存中每一个数据的访问情况,并根据数据的实时访问,调整数据在链表中的位置,然后通过数据在链表中的位置,来表示数据是最近刚访问的,还是已经有一段时间没有访问了。 而具体来说,LRU 算法会把链表的头部和尾部分别设置为 MRU 端和 LRU 端。其中

子冉冰清的博客 2078

LRU缓存算法的实现

LRU LRU(Least Recently Used)是一种常见的页面置换算法,在计算中,所有的文件操作都要放在内存中进行,然而计算机内存大小是固定的,所以我们不可能把所有的文件都加载到内存,因此我们需要制定一种策略对加入到内存中的文件进项选择。 常见的页面置换算法有如下几种: LRU 最近最久未使用 FIFO 先进先出置换算法 类似队列 OPT 最佳置换算法 (理想中存在的) NRU C...

iKun 9万+

Redis的LRU缓存淘汰算法实现

1 标准LRU的实现原理 LRU,最近最少使用(Least Recently Used,LRU),经典缓存算法。 LRU会使用一个链表维护缓存中每个数据的访问情况,并根据数据的实时访问,调整数据在链表中的位置,然后通过数据在链表中的位置,表示数据是最近刚访问的,还是已有段时间未访问。 LRU会把链头、尾分别设为MRU端和LRU端: MRU,Most Recently Used 缩写,表示此处数据刚被访问 LRU端,此处数据最近最少被访问的数据 LRU可分成如下情况: case1:当有新数据插入,LRU

JavaEdge全是干货的技术号 3510

LRU算法

注意:(1)给双向链表添加头尾的虚拟节点,虚拟节点不包含信息,只起到连接的作用,这样的好处是不用再特殊考虑链表中有数据的第一个节点和最后一个节点,可以把这两个节点也当成普通节点一样对待了。Redis会基于server.maxmemory_samples配置选取固定数目的key,然后比较它们的lru访问时间,然后淘汰最近最久没有访问的key,maxmemory_samples的值越大,Redis的近似LRU算法就越接近于严格LRU算法,但是相应消耗也变高,对性能有一定影响,样本值默认为5。

weixin_47027760的博客 1772

LRU算法原理及实现,最近最少使用算法

LRU算法原理及实现,最近最少使用算法

26857259 1375

深入了解Redis内存淘汰策略中的LRU算法应用

LRU(Least Recently Used,最近最少使用)算法是一种常见的内存淘汰策略,它根据数据的访问时间来决定哪些数据会被淘汰。LRU算法的核心思想是:最久未被访问的数据,被认为是最不常用的数据,应该被优先淘汰。LRU 算法广泛应用在诸多系统内,例如 Linux 内核页表交换,MySQL Buffer Pool 缓存页替换,以及 Redis 数据淘汰策略。简单来说,可以认为LRU算法是在维护一个链表,每次操作某个数据,就把数据移到链表的头部。例如:向一个缓存空间依次插入三个数据 A/B/C,填满了缓

qq_42581682的博客 2064
上一篇: 解决:Linux下C++段错误不产生core文件
下一篇: epoll比select和poll究竟高效在哪儿(剖析及实现)
zsiming
博客等级 码龄7年 43粉丝 74原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值