LRUCache原理及源码实现

目录

LRUCache简介:

LRUCache的实现:

LinkedHashMap方法实现:

自己实现链表:


前言:

有需要本文章源码的友友请前往:LRUCache源码

LRUCache简介:

LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法。 什么是Cache?狭义的Cache指的是位于CPU和主存间的快速RAM, 通常它不像系统主存那样使DRAM技术,而使用昂贵但较快速的SRAM技术。 广义上的Cache指的是位于速度相差较大的两种硬件之间, 用于协调两者数据传输速度差异的结构。除了CPU与主存之间有Cache, 内存与硬盘之间也有Cache,乃至在硬盘与网络之间也有某种意义上的Cache── 称为Internet临时文件夹或网络内容缓存等。

Cache的容量有限,因此当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容。LRU Cache 的替换原则就是将最近最少使用的内容替换掉。其实, LRU译成最久未使用会更形象, 因为该算法每次替换掉的就是一段时间内最久没有使用过的内容。👍👍👍

LRUCache的实现:

实现LRUCache的方法和思路很多,但是要保持高效实现O(1)的put和get,那么使用双向链表哈希表的搭配是最高效和经典的。使用双向链表是因为双向链表可以实现任意位置O(1)的插入和删除,使用哈希表是因为哈希表的增删查改也是O(1)。 

具体如下图,这个图一定要记住,下面的代码都是围绕这个图来展开的。🌸🌸🌸

LRUCache是实现主要有两种方法:

(1)是使用JDK中类似LRUCahe的数据结构LinkedHashMap。

(2)自己实现双向链表+HashMap实现。

LinkedHashMap方法实现:

由于LinkdeHashMap和LRUCache非常相似,故我们直接继承它,直接调用它的方法就行,其实就是给LinkedHashMap套了个LRUCache的壳。😎😎😎

基本参数:

private int capacity;
public LRUCache(int capacity){
    super(capacity,0.75f,true);
    this.capacity = capacity;
}

super()是调用父亲的构造方法(LinkdeHashMap),其源码如下:

参数说明:

1. initialCapacity 初始容量大小,使用无参构造方法时,此值默认是16。

2. loadFactor 加载因子,使用无参构造方法时,此值默认是 0.75f。

3. accessOrder 如果为false基于插入顺序(后续会解释),如果为true基于访问顺序。

那么什么是loadFactor呢?

loadFactor(负载因子)是表示Hash表中元素的填满的程度。🌸🌸🌸

accessOrder决定的顺序是做什么的?

当accessOrder为false时,下面代码打印map结果如下:🌸🌸🌸

当accessOrder为true时,下面代码打印map的结果如下:

通过对比两张图片我们不难发现在map执行get后 " 1 "被放到map的末尾。这个性质就很好的符合LRUCache的性质,所以我们可以使用LinkedHashMap来模拟实现LRUCache。

具体代码如下:

public class LRUCache extends LinkedHashMap<Integer,Integer> {
    private int capacity;
    public LRUCache(int capacity){
        super(capacity,0.75f,true);
        this.capacity = capacity;
    }
    public int get(int key){
        return super.getOrDefault(key,-1);
    }
    public void put(int key,int value){
        super.put(key,value);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return super.size() > capacity;
    }
}

基本都是调用LinkedHashMap的方法,故这里不再过多赘述,唯一需要注意的一点是:removeEldestEntry方法必须要重写,因为在其源码中是默认返回false。

下面有一道关于LRUCache的oj题目,友友们可以用实现后的代码去跑一跑。

LRU缓存

自己实现链表:

链表的节点,这里采用的是带头节点和带尾节点的双向链表(实现非常方便)。节点和HashMap对应,采用静态内部类。

static class DLinkNode{
        public int key;//对应map的key
        public int val;//对应map的value
        public DLinkNode next;//后指针
        public DLinkNode prev;//前指针
        public DLinkNode(int key,int val){
            this.key = key;
            this.val = val;
        }
        public DLinkNode(){

        }
    }

LRUCache的全局变量及构造方法如下:这里需要注意要把head节点和tail节点之间相互指向一下,不然会空指针异常,顺便把HashMap初始好。

    DLinkNode head;//头节点
    DLinkNode tail;//尾节点
    public int capacity;//LRUCache的容量
    public int usedSize;//LRUCache中的节点个数
    Map<Integer,DLinkNode> cache;
    public MyLRUCache(int capacity){
        this.capacity = capacity;
        head = new DLinkNode();//创建头节点
        tail = new DLinkNode();//创建尾节点
        head.next = tail;//将头尾节点相互指向,防止空指针异常
        tail.prev = head;
        cache = new HashMap<>();
    }

查找key:

首先利用哈希表以O(1)的时间复杂度完成查找key对应的节点,拿到节点后将其移动到尾节点同时返回对应的节点值。移动节点到尾节点分为两步,1.删除当前节点 2.尾插新节点。为了使代码更加简洁使用函数独立实现实现。

/**
     * 查找key对应节点如果找不到返回-1,如果有将它放到尾节点
     * @param key
     * @return
     */
    public int get(int key){
        DLinkNode node = cache.get(key);
        //不存在直接返回-1
        if(node == null){
            return -1;
        }

        //存在的情况
        //将node节点移动到末尾
        //1.删除当前节点
        //2.尾插新节点
        moveTail(node);
        //3.返回值
        return node.val;
    }

moveTail源码如下:

下面都是一些简单的链表操作,画个图就没有什么问题了。

 private void moveTail(DLinkNode node){
        //1.删除node节点
        remove(node);
        //2.将node查到末尾
        addToTail(node);
    }

    /**
     * 将node节点添加到尾节点
     * @param node
     */
    private void addToTail(DLinkNode node){
        tail.prev.next = node;
        node.prev = tail.prev;
        node.next = tail;
        tail.prev = node;
    }
    /**
     * 将node节点删除
     * @param node
     */
    private void remove(DLinkNode node){
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

效果如下: 

插入节点:

对于一般数据结构来说插入和删除节点是所有基本操作中最难的,在LRUCache的插入中如果节点大于一个临界值的话,插入节点后要进行删除不常用的节点(头节点)😭😭😭。要分为节点已经存不存在两种情况来分情况讨论,如果已经存在的话就要更新节点对应的值,如果不存在的话先把节点插入末尾后再加入哈希表长度 + 1,当超过capacity是要把头节点删除同时要把它在哈希表的映射关系删除掉。🤩🤩🤩

public void put(int key,int val) {
        DLinkNode node = cache.get(key);
        if (node != null) {
            //1.如果key已经存在
            node.val = val;//更新节点的值
            moveTail(node);//将node节点移动到尾节点
        }else {
            //2.如果key不存在
            node = new DLinkNode(key, val);
            addToTail(node);
            cache.put(key, node);//将node节点添加到哈希表中
            usedSize++;
            //当超过LRUCache的容量
            if (usedSize > capacity) {
                DLinkNode removeNode = head.next;//要删除的节点,方便后续操作
                removeHead(removeNode);//删除头节点
                cache.remove(removeNode.key);
                usedSize--;
            }

        }
    }

删除同节点(removeHead)对应的源码如下:

画图画图画图🐳🐳🐳

private void removeHead(DLinkNode node){
        head.next = node.next;
        node.next.prev = head;
    }

对应效果如下:这里可能在get(2)这里可能有的友友不太理解,这是因为get(1)后会把节点1放在尾节点,2就成头节点了,所以在插入3节点是删除是2节点而不是1节点。 

一样的在实现完代码后可以拿到上面LinkedHashMap给出的例题去跑一跑。 

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

扣子Coze插件教程:如何使用Coze IDE创建插件

&#x1f9d9;‍♂️ 诸位好&#xff0c;吾乃斜杠君&#xff0c;编程界之翘楚&#xff0c;代码之大师。算法如流水&#xff0c;逻辑如棋局。 &#x1f4dc; 吾之笔记&#xff0c;内含诸般技术之秘诀。吾欲以此笔记&#xff0c;传授编程之道&#xff0c;助汝解技术难题。 &#…

通过一篇文章让你了解Linux的重要性

Linux 前言一、什么是Linux后台vs前台为何大多数公司选择使用Linux作为后台服务器 二、Linux的背景介绍UNIX发展的历史Linux发展历史开源官网发行版本DebianUbuntu红帽企业级LinuxCentOSFedoraKali Linux 三、国内企业后台和用户使用Linux现状IT服务器Linux系统应用领域嵌入式L…

每日OJ题_01背包④_力扣1049. 最后一块石头的重量 II

目录 力扣1049. 最后一块石头的重量 II 问题解析 解析代码 滚动数组优化代码 力扣1049. 最后一块石头的重量 II 1049. 最后一块石头的重量 II 有一堆石头&#xff0c;用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合&#xff0c;从中选出任意…

挑战全网,看谁能用栈和队列解决更多问题

1.栈 2.队列 3.栈和队列面试题 正文开始&#xff1a; 1. 栈 1.1 栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶&#xff0c;另一端称为栈底。 栈中的数据元素遵守 后…

项目之旅(前两周)

文章目录 学习总结input1.text 文本框2.password 密码框3.button 按钮4.file 文件还可定义上传类型 5.日期6.radio 单选框7. checkbox 复选框 项目总结生活总结 学习总结 input 本次写项目时才发现&#xff0c;input有很多种用法&#xff0c;这里列举几种 1.text 文本框 不…

嵌入式音视频进阶学习(建议收藏!)

前言&#xff1a; 大家好&#xff0c;今天花点时间&#xff0c;整理一下最近看的一些音视频英文文档资料和相关的一些音视频书籍&#xff0c;下面分享的资料&#xff0c;仅是个人的一个学习&#xff0c;仅供参考&#xff01; rtp学习&#xff1a; 在这里给大家汇总的资料&#…

如何在Linux部署MeterSphere并实现公网访问进行远程测试工作

文章目录 前言1. 安装MeterSphere2. 本地访问MeterSphere3. 安装 cpolar内网穿透软件4. 配置MeterSphere公网访问地址5. 公网远程访问MeterSphere6. 固定MeterSphere公网地址 前言 MeterSphere 是一站式开源持续测试平台, 涵盖测试跟踪、接口测试、UI 测试和性能测试等功能&am…

LlamaIndex 文档1

文章目录 关于 LlamaIndex&#x1f680; 为什么要进行上下文增强&#xff1f;&#x1f999; 为什么使用 LlamaIndex 进行上下文增强&#xff1f;&#x1f468;‍&#x1f469;‍&#x1f467;‍&#x1f466; LlamaIndex 适合谁&#xff1f; 入门&#x1f5fa;️生态系统社区相…

C++引用和右值引用

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

计算机网络 Cisco静态路由实验

一、实验要求与内容 1、路由器的基本配置 &#xff08;1&#xff09;命名 &#xff08;2&#xff09;关闭域名解析 &#xff08;3&#xff09;设置路由接口IP地址 2、配置静态路由以实现所有客户机都能互相通信 3、配置默认路由 4、了解ping命令和trace&#xff08;跟踪…

Bug的定义生命周期

1、bug的定义 你们觉得bug是什么? 软件的Bug狭义概含是指软件程序的漏洞或缺陷&#xff0c; 广义概念除此之外还包括测试工程师或用户所发现和提出的软件可改进的细节(增强性&#xff0c;建议性)、或 与需求文档存在差异的功能实现等。 我们的职责就是&#xff0c;发现这些B…

正则表达式 速成

正则表达式的作用 正则表达式&#xff0c;又称规则表达式,&#xff08;Regular Expression&#xff0c;在代码中常简写为regex、regexp或RE&#xff09;&#xff0c;是一种文本模式&#xff0c;包括普通字符&#xff08;例如&#xff0c;a 到 z 之间的字母&#xff09;和特殊字…

轻量级 S3 协议存储客户端

目前大家一般不会把二进制文件直接放在应用服务器上&#xff0c;而是存在“对象存储”的方案中&#xff0c;例如亚马逊的 AWS&#xff0c;阿里云的 OSS、Cloudflare R2 等。AWS 为最早的始作俑者&#xff0c;因此其 S3 协议也近乎标准化&#xff0c;各大厂商的对象存储方案都实…

【C++类和对象】构造函数与析构函数

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

【grpc】grpc进阶二,grpc认证方式

本章把之前的工程结构改了一下&#xff0c;创建了 server 和 client 两个目录&#xff0c;分别把 server.go&#xff0c;client.go 移动过去。 接下来会介绍 grpc 的 TLS 认证和 Oauth2 一、TLS认证 在进行功能验证是需要使用 openssl 创建自有证书&#xff0c;下面是创建步骤…

Paddle实现人脸对比(二)

我之前发过一篇基于孪生网络的人脸对比的文章&#xff0c;这篇文章也到了百度的推荐位置&#xff1a; 但是&#xff0c;效果并不是很好。经过大量的搜索&#xff0c;我发现了一种新的方法&#xff0c;可以非常好的实现人脸对比。 原理分析 我们先训练一个普通的人脸分类模型&…

关于机器学习/深度学习的一些事-答知乎问(二)

进化算法与深度强化学习算法结合如何进行改进&#xff1f; &#xff08;1&#xff09;进化算法普遍存在着样本效率低下的问题&#xff0c;虽然其探索度较高&#xff0c;但其本质为全局随机性搜索&#xff0c;需要在整个回合结束后才能更新其种群&#xff0c;而深度强化学习在每…

深入理解计算机网络分层结构

一、 为什么要分层&#xff1f; 计算机网络分层的主要目的是将复杂的网络通信过程分解为多个相互独立的层次&#xff0c;每个层次负责特定的功能。这样做有以下几个好处&#xff1a; 模块化设计&#xff1a;每个层次都有清晰定义的功能和接口&#xff0c;使得网络系统更易于设…

023——搭建图形化客户端(基于pySimpleGUI)

目录 一、pysimplegui 1.1 安装 1.2 测试 二、 pysimplegui学习 2.1 学习地址 2.2 人类早期驯服pysimplegui珍贵流水账 三、 实现项目专属的界面 一、pySimpleGUI 1.1 安装 pip install pysimplegui -i https://pypi.tuna.tsinghua.edu.cn/simple Command pip not fo…