LRUCache原理及源码实现

2024-04-14 14:52
文章标签 实现 源码 原理 lrucache

本文主要是介绍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给出的例题去跑一跑。 

结语:

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

这篇关于LRUCache原理及源码实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/903279

相关文章

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4