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);}@Overrideprotected 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的keypublic int val;//对应map的valuepublic 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);//不存在直接返回-1if(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给出的例题去跑一跑。 

结语:

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

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

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

相关文章

ChatGPT-4 Turbo 今天开放啦!附如何查询GPT-4 是否为 Turbo

2024年4月12日&#xff0c;OpenAI在X上宣布GPT-4 Turbo开放了&#xff01;提高了写作、数学、逻辑推理和编码方面的能力。另外最重要的是&#xff0c;响应速度更快了&#xff01;&#xff01; ChatGPT4 Turbo 如何升级&#xff1f;解决国内无法升级GPT4 Turbo的问题&#xff0…

设计模式-代理模式(Proxy)

1. 概念 代理模式&#xff08;Proxy Pattern&#xff09;是程序设计中的一种结构型设计模式。它为一个对象提供一个代理对象&#xff0c;并由代理对象控制对该对象的访问。 2. 原理结构图 抽象角色&#xff08;Subject&#xff09;&#xff1a;这是一个接口或抽象类&#xff0…

ros2 launch gazebo_ros gazebo.launch.py无法启动

我的系统是ubuntu20.04&#xff0c;ros2的版本是humble&#xff0c;当运行gazebo仿真时&#xff0c;运行 ros2 launch gazebo_ros gazebo.launch.py命令&#xff0c;会出现以下问题&#xff1a; 此时&#xff0c;这个页面会卡死在第六行&#xff0c;gazebo也不会打开 但最后单…

Element Plus的deep穿透

Element Plus的deep穿透主要用于解决在Vue3项目中使用Element Plus组件库时&#xff0c;样式设置不生效的问题。当直接在Element Plus组件上使用样式时&#xff0c;由于Element Plus的样式是通过外部样式文件实现的&#xff0c;这些样式的优先级更高&#xff0c;因此直接添加的…

009优化器

优化器的逻辑 优化器选择索引的目的是选择一个执行方案&#xff0c;用最小的代价去执行语句。 数据库里影响执行代价的因素。 1&#xff09;扫描行数 &#xff0c;扫描的行数越少&#xff0c;意味着访问磁盘的数据的次数越小&#xff0c;消耗cpu的资源越少 2&#xff09;是…

threejs--02threejs手册起步+进阶

快速过一下基本用法 01 场景 //场景、相机和渲染器 const scene new THREE.Scene(); const camera new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );const renderer new THREE.WebGLRenderer(); renderer.setSize( window.innerWi…

哈希函数算法

概述 为了实现哈希集合这一数据结构&#xff0c;有以下几个关键问题需要解决&#xff1a; 哈希函数&#xff1a;能够将集合中任意可能的元素映射到一个固定范围的整数值&#xff0c;并将该元素存储到整数值对应的地址上。冲突处理&#xff1a;由于不同元素可能映射到相同的整…

【vue】Pinia-1 入门

简介 Pinia是Vue.js的一个状态管理库&#xff0c;由Vue.js官方维护。它提供了一种简单而强大的方式来管理Vue.js应用程序中的状态。 应用场景&#xff1a;用户登录 解决问题 Pinia是全局的状态管理&#xff0c;所有组件都可看到 避免了组件间的大量数据交换简化了组件间的通…

C语言中局部变量和全局变量是否可以重名?为什么?

可以重名 在C语言中, 局部变量指的是定义在函数内的变量, 全局变量指的是定义在函数外的变量 他们在程序中的使用方法是不同的, 当重名时, 局部变量在其所在的作用域内具有更高的优先级, 会覆盖或者说隐藏同名的全局变量 具体来说: 局部变量的生命周期只在函数内部,如果出了…

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

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

Stacked Hourglass Networks for Human Pose Estimation 用于人体姿态估计的堆叠沙漏网络

Stacked Hourglass Networks for Human Pose Estimation 用于人体姿态估计的堆叠沙漏网络 这是一篇关于人体姿态估计的研究论文&#xff0c;标题为“Stacked Hourglass Networks for Human Pose Estimation”&#xff0c;作者是 Alejandro Newell, Kaiyu Yang, 和 Jia Deng&a…

多模态 ——LLaVA 集成先进图像理解与自然语言交互GPT-4的大模型

概述 提出了一种大型模型 LLaVA&#xff0c;它使用 GPT-4 生成多模态语言图像指令跟随数据&#xff0c;并利用该数据将视觉和语言理解融为一体。初步实验表明&#xff0c;LLaVA 展示了出色的多模态聊天能力&#xff0c;在合成多模态指令上的表现优于 GPT-4。 在科学质量保证中…

如何减少延迟队列的压力

1、增加消费者数量&#xff1a; 通过增加消费者&#xff08;即处理消息的工作进程&#xff09;的数量&#xff0c;可以提高消息的处理速度&#xff0c;从而减轻队列的压力。这可以 通过水平扩展来实现&#xff0c;即部署更多的消费者实例来并行处理消息。 2、优化消费者性能&…

第1章、react基础知识;

一、react学习前期准备&#xff1b; 1、基本概念&#xff1b; 前期的知识准备&#xff1a; 1.javascript、html、css&#xff1b; 2.构建工具&#xff1a;Webpack&#xff1a;https://yunp.top/init/p/v/1 3.安装node&#xff1a;npm&#xff1a;https://yunp.top/init/p/v/1 …

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之三 简单动态聚光灯效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之三 简单动态聚光灯效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之三 简单动态聚光灯效果 一、简单介绍 二、简单动态聚光灯效果实现原理 三、简单动态聚光灯效果…

Mysql视图与事物与字符集实验

一 视图 1.视图的定义 视图是一个虚拟表&#xff0c;其内容由查询定义。 2.视图的优点 1&#xff09;视点集中 2&#xff09;简化操作 3&#xff09;定制数据 4&#xff09;分隔合并数据 5&#xff09;安全性好 3.语法格式及限定条件 1&#xff09;语法格式&#xff1…

详细讲解 C 语言标准库中的 strncmp 函数

strncmp 是 C 语言标准库提供的一个函数&#xff0c;用于比较两个字符串的前若干个字符是否相等。以下是关于 strncmp 函数的详细说明&#xff1a; 函数原型 int strncmp(const char *str1, const char *str2, size_t num); 参数说明 const char *str1: 指向第一个要比较的字…

轻量化模块整理,即插即用

轻量化模块整理&#xff0c;即插即用&#xff08;持续更新&#xff09; 整理一些轻量化的结构&#xff0c;作为知识储备&#xff0c;可以用到后续的项目和研究中 Mobilenetv3 深度可分离卷积 MobileNetV3 是一个轻量级的深度学习模型&#xff0c;专为移动和边缘设备上的高效…

微信小程序二维码

目录 前言小程序二维码不限制数量的小程序码微信小程序官方文档&#xff1a;获取不限制数量的小程序码小程序里面中接收不限制数量的小程序码传递的参数 普通链接二维码微信小程序官方文档&#xff1a;扫普通链接二维码打开小程序小程序里面中接收普通链接二维码传递的参数 前言…

C#面:泛型的主要约束和次要约束是什么

在 C# 中&#xff0c;泛型的约束是用来限制泛型类型参数的行为和能力的。 主要约束和次要约束是两种不同的约束方式。 主要约束&#xff08;Primary Constraint&#xff09;&#xff1a; 主要约束指定了泛型类型参数必须满足的最基本的条件&#xff0c;它可以是一个类、一个接…