LRU缓存淘汰算法详解与实现

news2024/11/14 15:29:23

目录

1.什么是LRU算法

2.LRU算法原题描述

3.LRU算法设计

4.LRU算法细节分析

5.代码实现


1.什么是LRU算法

就是一种缓存淘汰策略

计算机的缓存容量有限,如果缓存满了就要删除一些内容,给新内容腾位置。但问题是,删除哪些内容呢?我们肯定希望删掉哪些没什么用的缓存,而把有用的数据继续留在缓存里,方便之后继续使用。那么,什么样的数据,我们判定为「有用的」的数据呢?

LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently Used,也就是说我们认为最近使用过的数据应该是是「有用的」,很久都没用过的数据应该是无用的,内存满了就优先删那些很久没用过的数据。

2.LRU算法原题描述

举一个例子:

假设我的手机只允许我同时开 3 个应用程序,现在已经满了。那么如果我新开了一个应用「时钟」,就必须关闭一个应用为「时钟」腾出一个位置,关那个呢?

按照 LRU 的策略,就关最底下的,因为那是最久未使用的,然后把新开的应用放到最上面:

现在你应该理解 LRU(Least Recently Used)策略了。

我们先从一道LRU设计算法题开始

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。支持以下操作:

获取数据 get 和 写入数据 put 。获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。

写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

O(1) 时间复杂度内完成这两种操作

3.LRU算法设计

分析上面的操作过程,要让 put 和 get 方法的时间复杂度为 O(1),我们可以总结出 cache 这个数据结构必要的条件:查找快,插入快,删除快,有顺序之分

因为必须有顺序之分,以区分最近使用的和久未使用的数据;而且我们要查找键是否已存在;如果容量满了要删除最后一个数据;每次访问还要把数据插入到队头。

那么,什么数据结构同时符合上述条件呢?哈希表查找快,但是数据无固定顺序链表有顺序之分,插入删除快,但是查找慢。所以结合一下,形成一种新的数据结构:哈希链表

LRU 缓存算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体。这个数据结构长这样:

在双向链表中特意增加两个节点,不用来存储任何数据。使用节点,增加/删除节点的时候就可以不用考虑边界节点不存在情况,简化编程难度,降低代码复杂度。 

思想听起来很简单,就是借助哈希表赋予了链表快速查找的特性嘛:可以快速查找某个 key 是否存在缓存(链表)中,同时可以快速删除、添加节点。回想刚才的例子,这种数据结构是不是完美解决了 LRU 缓存的需求?

也许大家会问,为什么要是双向链表,单链表行不行?另外,既然哈希表中已经存了 key,为什么链表中还要存键值对呢,只存值不就行了?

这样设计的原因,必须等我们亲自实现 LRU 算法之后才能理解,所以我们开始一步步实现代码吧

4.LRU算法细节分析

新插入的元素或者最新查询的元素要放到链表的头部,对于长时间未访问的元素要放到链表尾部,所以每次插入或者查询都需要维护链表中元素的顺序

使用哈希表的原因是查询时间复杂度为O(1),使用双向链表的原因是对于删除和插入操作时间复杂度为O(1)。

其中哈希表中存储的 key 为 K,value 为 Node<K,V> 的引用,双向链表存储的元素为Node<K,V>的引用.

对于put操作:

①首先判断缓存中 元素 K 是否存在,如果存在,则把链表中的元素Node<K, V>删除,map中的数据<K, Node<K, V> >不用删除,再在链表头部插入元素,并更新map,直接返回即可 ;

②缓存不存在,并且缓存没有满的话,直接把元素插入链表的表头,缓存满了的话移除表尾元素(最旧未访问元素),将元素K插入表头,增加map中的<K, Node<K, V>>, 更新map。

对于get操作:

首先要判断 缓存中(map)是否存在,如果存在则把该节点删除并在链表头部插入该元素并更新map 返回当前元素即可,如果map不存在 则直接返回-1;

5.代码实现

public class LRUCache {

    Entry head, tail;
    int capacity;
    int size;
    Map<Integer, Entry> cache;


    public LRUCache(int capacity) {
        this.capacity = capacity;
        // 初始化链表
        initLinkedList();
        size = 0;
        cache = new HashMap<>(capacity   2);
    }

    /**
     * 如果节点不存在,返回 -1.如果存在,将节点移动到头结点,并返回节点的数据。
     *
     * @param key
     * @return
     */
    public int get(int key) {
        Entry node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // 存在移动节点
        moveToHead(node);
        return node.value;
    }

    /**
     * 将节点加入到头结点,如果容量已满,将会删除尾结点
     *
     * @param key
     * @param value
     */
    public void put(int key, int value) {
        Entry node = cache.get(key);
        if (node != null) {
            node.value = value;
            moveToHead(node);
            return;
        }
        // 不存在。先加进去,再移除尾结点
        // 此时容量已满 删除尾结点
        if (size == capacity) {
            Entry lastNode = tail.pre;
            deleteNode(lastNode);
            cache.remove(lastNode.key);
            size--;
        }
        // 加入头结点

        Entry newNode = new Entry();
        newNode.key = key;
        newNode.value = value;
        addNode(newNode);
        cache.put(key, newNode);
        size  ;

    }

    private void moveToHead(Entry node) {
        // 首先删除原来节点的关系
        deleteNode(node);
        addNode(node);
    }

    private void addNode(Entry node) {
        head.next.pre = node;
        node.next = head.next;

        node.pre = head;
        head.next = node;
    }

    private void deleteNode(Entry node) {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }


    public static class Entry {
        public Entry pre;
        public Entry next;
        public int key;
        public int value;

        public Entry(int key, int value) {
            this.key = key;
            this.value = value;
        }

        public Entry() {
        }
    }

    private void initLinkedList() {
        head = new Entry();
        tail = new Entry();

        head.next = tail;
        tail.pre = head;

    }

    public static void main(String[] args) {

        LRUCache cache = new LRUCache(2);

        cache.put(1, 1);
        cache.put(2, 2);
        System.out.println(cache.get(1));
        cache.put(3, 3);
        System.out.println(cache.get(2));

    }
}

如果大家完全理解了LRU算法,那么练习结果这个相信大家一眼就可以看出:

分析:

1.首先我们给出的容量是2,先push 1 后push 2,那么现在数据存储顺序应该是2,1

2.我们接下来对于1这个值进行了查询,因为1这个值存在,所以1这个值被引用了一次,1这个值放到最上边,返回1,现在的顺序是1,2

3.现在push数据3,因为容量只有2,所以淘汰掉末尾数据2,现在顺序变为3,1

4.最后查询数据2,因为没有数据2,所以返回-1;顺序不变

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

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

相关文章

向专家学习:APP开发的成功窍门

尽管有诸多障碍&#xff0c;但 APP开发仍然是企业的最佳选择。开发一款成功的 APP应用程序是一项具有挑战性的工作&#xff0c;但您仍然可以在以下几个方面做出改进。 这是一个快速变化的行业&#xff0c;也是一个可以通过学习获得经验和知识的行业。如果您不知道如何解决问题…

STM32CUBUMX配置RS485 modbus STM32(从机)亲测可用

———————————————————————————————————— ⏩ 大家好哇&#xff01;我是小光&#xff0c;嵌入式爱好者&#xff0c;一个想要成为系统架构师的大三学生。 ⏩最近在开发一个STM32H723ZGT6的板子&#xff0c;使用STM32CUBEMX做了很多驱动&#x…

JSP--Java的服务器页面

jsp是什么&#xff1f; jsp的全称是Java server pages,翻译过来就是java的服务器页面。 jsp有什么作用&#xff1f; jsp的主要作用是代替Servlet程序回传html页面的数据&#xff0c;因为Servlet程序回传html页面数据是一件非常繁琐的事情&#xff0c;开发成本和维护成本都非常高…

zabbix5.0安装配置和日常管理使用笔记

服务端配置&#xff1a; zabbix官网&#xff08;有软件下载和安装步骤说明&#xff09; https://www.zabbix.com/cn/download 关selinux和防火墙 iptables -L 查看全部清空 建议给4G内存空间 获取zabbix下载镜像源 rpm -Uvh https://mirrors.aliyun.com/zabbix/zabbix/5.0/…

Slurm--资源管理系统

Slurm–资源管理系统 开源软件 SLURM 全称 Simple Linux Utility for Resource Management开源分布式资源管理软件可用于大型计算节点集群的高度可伸缩的集群管理器和作业调度系统 提供高效的资源与作业管理 状态监控资源管理作业调度 是用户使用计算资源的接口 作业提交 / 运…

【Vue3+Ts+Vite】配置页面切换过渡动画

文章目录 一、先看效果二、全量代码三、注意事项虽然Vue3支持 template 下存在多个根节点&#xff0c;但是 transition 过渡动画并不支持&#xff0c;要实现过渡动画的页面&#xff0c;都需要有一个根标签包裹页面内容&#xff0c;否则就会报如下警告: 四、相关文章友链本专栏记…

Vue 组件和计算属性(二)

一、组件 1.1 什么是组件 组件是可复用的 Vue 实例&#xff0c;说白了就是一组可以重复使用的模板&#xff0c;跟 JSTL 的自定义标签、Thymeleaf 的 th:fragment 等框架有着异曲同工之妙。通常一个应用会以一棵嵌套的组件树的形式来组织。 例如&#xff0c;你可能会有页头、侧…

vue + element UI Table 表格 利用插槽是 最后一行 操作 的边框线 不显示

在屏幕比例100%时 el-table添加border属性 使用作用域插槽 会不显示某侧的边框线&#xff0c;屏幕比例缩小或放大都展示 // 修复列的 边框线消失的bug thead th:not(.is-hidden):last-child {right:-1px;// 或者//border-left: 1px solid #ebeef5; } .el-table__row{td:not(.i…

Docker 容器转为镜像

# 容器转成镜像并指定镜像名称与版本号 # commit 时原有容器挂载的目录是不会被写入到新的镜像中去的&#xff0c;数据卷相关的都不会生效 # 但是 root 目录下新建的内容会写入到新的镜像中去 $ docker commit 容器ID 新镜像名称:版本号 $ docker commit -m"描述信息"…

2023年电赛---运动目标控制与自动追踪系统(E题)OpenMV方案

前言 &#xff08;1&#xff09;废话少说&#xff0c;很多人可能无法访问GitHub&#xff0c;所以我直接贴出可能要用的代码。此博客还会进行更新&#xff0c;先贴教程和代码 &#xff08;2&#xff09;视频教程&#xff1a; https://singtown.com/learn/49603/ &#xff08;3&a…

大数据技术之Clickhouse---入门篇---数据类型、表引擎

星光下的赶路人star的个人主页 今天没有开始的事&#xff0c;明天绝对不会完成 文章目录 1、数据类型1.1 整型1.2 浮点型1.3 布尔型1.4 Decimal型1.5 字符串1.6 枚举类型1.7 时间类型1.8 数组 2、表引擎2.1 表引擎的使用2.2 TinyLog2.3 Memory2.4 MergeTree2.4.1 Partition by分…

华为OD机试真题 JavaScript 实现【取出尽量少的球】【2023Q1 200分】,附详细解题思路

目录 一、题目描述游戏规则如下&#xff1a;限制规则一&#xff1a;限制规则二&#xff1a; 二、输入描述三、输出描述四、解题思路五、JavaScript算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 刷的越多&#xff0c;抽中…

《golang设计模式》第一部分·创建型模式-04-抽象工厂模式(Abstract Factory)

文章目录 1. 概述1.1 角色1.2 类图 2. 代码示例2.1 设计2.2 代码2.3 类图 1. 概述 1.1 角色 AbstractFactory&#xff08;抽象工厂&#xff09;&#xff1a;它声明了一组用于创建产品的方法&#xff0c;每一个方法对应一种产品。ConcreteFactory&#xff08;具体工厂&#xf…

phpstudy 进行 composer 全局配置

背景 因为注意到&#xff0c;使用 phpStudy 进行环境搭建时&#xff0c;有时需要使用 composer 每次都需要查找资料进行配置&#xff0c; 在此进行记录笔记&#xff0c;方便有需要的道友借鉴 配置 版本&#xff1a;composer1.8.5&#xff08;phpStudy8 当前只能安装这一个版本…

CAD批量转PDF的简单方法,三个步骤轻松完成转换

PDF格式的图纸可以在各种设备和软件上打开&#xff0c;因为PDF是一种跨平台的格式&#xff0c;不受操作系统或软件版本的影响。这意味着CAD图纸可以更容易地在不同的设备和操作系统之间传输&#xff0c;而无需担心兼容性问题&#xff0c;可以使图纸更易于共享、浏览和保护&…

Vue进阶(幺叁陆): transition标签实现页面跳转动画

文章目录 一、前言二、方案实现三、延伸阅读 transition标签四、拓展阅读 一、前言 在Vue项目开发过程中&#xff0c;应用全家桶vue-router实现路由跳转&#xff0c;且页面前进、后退跳转过程中&#xff0c;分别对应不同的切换动画。vue-router 切换页面时怎么设置过渡动画&am…

Pytorch深度学习-----神经网络之非线性激活的使用(ReLu、Sigmoid)

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用&#xff08;ToTensor&#xff0c;Normalize&#xff0c;Resize &#xff0c;Co…

Doccano工具安装教程/文本标注工具/文本标注自己的项目/NLP分词器工具/自然语言处理必备工具/如何使用文本标注工具

这篇文章是专门的安装教程&#xff0c;后续的项目创建&#xff0c;如何使用&#xff0c;以及代码部分可以参考这篇文章&#xff1a; NER实战&#xff1a;(NLP实战/命名实体识别/文本标注/Doccano工具使用/关键信息抽取/Token分类/源码解读/代码逐行解读)_会害羞的杨卓越的博客-…

【uniapp 样式】使用setStorageSync存储历史搜索记录

<template><view><view class"zhuangbox u-flex"><u--inputplaceholder"请输入关键字搜索"border"surround"shapecircleprefixIcon"search"prefixIconStyle"font-size: 22px;color: #909399"v-model&q…

vSphere ESXI 7.0 网络规划

ESXi 网络 业务网络、Vmotion&#xff08;漂移&#xff09;、管理网络、存储网络 ESXi 管理网络 vCenter Server 管理网络 vCenter Server SSO域名 Single Sign-on域名&#xff1a;在没有指定的情况下&#xff0c;默认填写 vsphere.local VMware vSphere整体解决方案和网络…