Java——LRUCache

article/2025/8/25 4:14:13

概念

简单来说,由于我们的空间是有限的,所以发明了这个数据结构,当我们的空间不够添加新的元素时,就会删除最近最少使用的元素。

其底层逻辑通过哈希表和链表共同实现。哈希表中存储链表的每一个元素,方便进行元素的获取,而链表则是为了方便移动元素,减少时间复杂度。

当一个元素被添加或者被访问时,其会移动到链表的尾部。因此,越靠近链表尾部的元素,越是最近访问次数多的元素,反之,越靠近头部则是最近不访问的。因此当链表的长度达到我们预设的某一个值时,我们只需要将链表的头部的元素删除,再把哈希表中的元素删除即可。

LinkedHashMap

在JDK中,就有类似于LRUCache的数据结构——LinkedHashMap,下面来介绍一下使用方法

其构造方法有三个参数,第一个参数是初始容量,第二个参数是负载因子(HashMap在多大的时候扩容,默认是0.75f)而第三个参数则是一个布尔值。

当第三个参数为false时,我们的链表的顺序就是基于插入的顺序排列的,先插进来的靠近头,后插进来的靠近尾部。当我们删除时,就会直接删除最先插入的节点

而当第三个参数为true时,链表中元素的排序则是基于访问的顺序的,最近访问过的就放到链表的尾部,也就是我们LRUCache的概念,因此我们把这个值设置为true

LinkedHashMap<String, Integer> linkedHashMap = new java.util.LinkedHashMap<>(16,0.7f,true);

使用put可以插入键值对,使用get则可以通过键访问对应的值。可以看到,当我们获取hi对应的值时,我们的链表顺序变成hi这个键值对在最后了


linkedHashMap.put("hello",1);
linkedHashMap.put("hi",2);
linkedHashMap.put("world",1);System.out.println(linkedHashMap);
System.out.println(linkedHashMap.get("hi"));
System.out.println(linkedHashMap);

在这里插入图片描述

LRUCache

而基于JDK的LinkedHashMap,我们可以实现一个LRUCache

首先继承于LinkedHashMap

public class LRUCache extends LinkedHashMap<Integer,Integer>

定义一个容量,当我们的链表数据大于这个容量的个数,就删除头部的元素

public int capacity;

构造方法通过重写LinkedHashMap进行构造,然后对capacity进行赋值

LRUCache(int capacity){//基于访问顺序super(capacity,0.75f,true);this.capacity = capacity;
}

然后只需要重写put和get方法,使用默认的put和get方法即可

@Override
public Integer put(Integer key, Integer value) {return super.put(key, value);
}@Override
public Integer get(Object key) {return super.get(key);
}

最后只需要重写一下其中的removeEldestEntry,这个方法是为真时删除最老的元素,所以我们只需要写当size()大于capacity时即可

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

验证

public static void main(String[] args) {LRUCache lruCache = new LRUCache(3);lruCache.put(1,10);lruCache.put(2,20);lruCache.put(3,30);System.out.println(lruCache);System.out.println(lruCache.get(2));System.out.println(lruCache);lruCache.put(4,40);System.out.println(lruCache);
}

在这里插入图片描述
可以看到,当我们的容量为3时,添加三个元素后,获取2对应的val,那么2:20这个键值对就会移动到链表的尾部,接着,我们再插入4:40这个键值对,我们的空间不够了,就会删除头部的键值对——3:30

MyLRUCache

我们也可以不借助LinkedHashMap,自己通过链表和HashMap来写一个LRUCache

定义链表中的节点

我们的链表使用的是双向带头带尾链表,因此在定义时需要定义prev和next,并且其中存储的是键值对,因此需要定义key和val

static class DLinkNode {public int key;public int val;public DLinkNode prev;public DLinkNode next;public DLinkNode(int key, int val){this.key = key;this.val = val;}public DLinkNode(){}@Overridepublic String toString() {return "DLinkNode{" +"key=" + key +", val=" + val +'}';}
}

参数

MyLRUCache中需要存储链表的头尾节点,元素的个数,哈希表和容量

public DLinkNode head;
public DLinkNode tail;
public int usedSize;//双向链表中有效的数据个数
public Map<Integer, DLinkNode> cache;
public int capacity;//容量

构造方法

构造方法需要初始化这些参数,并且需要将头尾节点互相连接,防止后面的插入操作会出现空指针异常

public MyLRUCache(int capacity){this.head = new DLinkNode();this.tail = new DLinkNode();head.next = tail;tail.prev = head;this.cache = new HashMap<>();this.capacity = capacity;
}

put方法

put方法需要先在哈希表中看这个key是否存储过

如果没有存储过,那么需要先创建一个节点,赋值key和val,将这个节点存储在哈希表中,然后将这个节点存储到链表的尾部,然后让usedSize++,判定usedSize是否超过了capacity,如果超过了,那么需要将链表的头节点删除,并且在哈希表中删除,再让usedSize–

而如果存储过了这个值,那么只需要在哈希表中获取这个节点,更新一下key对应的val,然后将这个节点移动到尾部即可。

public void put(int key, int val){//查找当前key是否存储过DLinkNode node = cache.get(key);if(node == null){//当前key没有存储//实例化一个节点DLinkNode dLinkNode = new DLinkNode(key, val);//存储到map中cache.put(key,dLinkNode);//存储到链表的尾巴addTail(dLinkNode);usedSize++;//检查当前的链表数据个数是否达到capacityif(usedSize > capacity){//链表数据个数超过capacity,移除头部节点DLinkNode remNode = removeHead();cache.remove(remNode.key);usedSize--;}} else {//当前key存储过//更新key对应的valnode.val = val;//将该节点移动到尾部moveToTail(node);}printNodes("put");
}

moveToTail

先移除这个节点,然后将这个节点添加到链表尾部

/*** 将当前节点移动到尾部* @param node*/
private void moveToTail(DLinkNode node) {removeNode(node);addTail(node);
}private void removeNode(DLinkNode node) {node.prev.next = node.next;node.next.prev = node.prev;
}/*** 添加节点到链表尾部* @param node*/
private void addTail(DLinkNode node){tail.prev.next = node;node.prev = tail.prev;node.next = tail;tail.prev = node;
}

removeHead

将节点从头部删除,并返回该节点

public DLinkNode removeHead(){DLinkNode del = head.next;head.next = del.next;del.next.prev = head;return del;
}

get方法

get方法则比较简单,拿key到哈希表中找val,如果不存在则直接返回-1,如果存在,那么将这个节点放到链表的尾部,然后返回这个key对应的val

/*** 访问key对应的节点* @param key* @return*/
public int get(int key){DLinkNode dLinkNode = cache.get(key);if(dLinkNode == null){return -1;}//将节点放到链表尾部moveToTail(dLinkNode);printNodes("get");return dLinkNode.val;
}

print

这个方法是用来打印链表中所有的节点的

public void printNodes(String str){System.out.println(str + ": ");DLinkNode dLinkNode = head.next;while(dLinkNode != tail){System.out.print(dLinkNode);dLinkNode = dLinkNode.next;System.out.println();}
}

测试

还使用上一个测试使用的数据集,可以看到最终的结果是相同的

public static void main(String[] args) {MyLRUCache lruCache = new MyLRUCache(3);lruCache.put(1,10);lruCache.put(2,20);lruCache.put(3,30);lruCache.get(2);lruCache.put(4,40);
}

在这里插入图片描述


http://chatgpt.dhexx.cn/article/GihxgusB.shtml

相关文章

LruCache缓存

Lru算法&#xff1a; Lru 指的是“Least Recently Used-近期最少使用算法”。 1、那么LruCache到底是什么呢&#xff1f; LruCache 是对限定数量的缓存对象持有强引用的缓存&#xff0c;每一次缓存对象被访问&#xff0c;都会被移动到队列的头部。当有对象要被添加到已经达到数…

LruCache 源码解析

1. 概述 对于 Android 开发者&#xff0c;LruCache 肯定不陌生&#xff0c;几乎所有的图片缓存框架都会用到它来实现内存缓存等&#xff0c;可见 LruCache 在 Android 开发中的重要性。LRU 是 Least Recently Used 的缩写&#xff0c;近期最少使用的意思。当我们进行缓存的时候…

LruCache

LruCache这个类是通过Glide得知的&#xff0c;不过它是自己又基于LRU算法自己写了个LruCache工具类&#xff0c;不过基本原理类似&#xff0c;都是基于LRU算法实现的 1.来源 一般来说&#xff0c;缓存策略主要包含缓存的添加、获取和删除这三类操作。如何添加和获取缓存这个比…

Android基础-LruCache原理解析

一、Android中的缓存策略 一般来说&#xff0c;缓存策略主要包含缓存的添加、获取和删除这三类操作。如何添加和获取缓存这个比较好理解&#xff0c;那么为什么还要删除缓存呢&#xff1f;这是因为不管是内存缓存还是硬盘缓存&#xff0c;它们的缓存大小都是有限的。当缓存满了…

LRUCache详解

1.概念 LRU是Least Recently Used的缩写&#xff0c;意思是最近最少使用&#xff0c;它是一种Cache替换算法。 Cache的容量有限&#xff0c;因此当Cache的容量用完后&#xff0c;而又有新的内容需要添加进来时&#xff0c; 就需要挑选并舍弃原有的部分内容&#xff0c;从而腾出…

LRUCache 详解

LRU算法详解 一、什么是 LRU 算法 就是一种缓存淘汰策略。 计算机的缓存容量有限&#xff0c;如果缓存满了就要删除一些内容&#xff0c;给新内容腾位置。但问题是&#xff0c;删除哪些内容呢&#xff1f;我们肯定希望删掉哪些没什么用的缓存&#xff0c;而把有用的数据继续…

LRU Cache

前言 哈喽&#xff0c;各位小伙伴大家好&#xff0c;本章内容为大家介绍计算机当中为了提高数据相互传输时的效率而引进的一种重要设计结构叫做LRU Cache,下面将为大家详细介绍什么是LRU Cache,以及它是如何是实现的&#xff0c;如何提升效率的。 1.什么是LRU Cache? LRU是L…

uniapp日历原生插件

<template><!-- 打卡日历页面 --><view classall><view class"bar"><!-- 上一个月 --><view class"previous" click"handleCalendar(0)"><button class"barbtn">上一月</button><…

微信小程序使用日历插件

一&#xff0c;添加插件 1&#xff0c;在你的小程序关联的微信公众平台打开 设置》第三方服务》添加插件 2&#xff0c;直接AppID&#xff08;wx92c68dae5a8bb046&#xff09;搜索到该插件并申请授权&#xff0c;授权成功即可在小程序使用 二&#xff0c;小程序使用插件 app…

日历组件

日历组件&#xff1a; <template><div class"calendar" click.stop><div class"input-wrap"><inputtype"text"v-if"dateChangeSets":placeholder"placeholder"class"input dateChangeSets middle…

vue-calendar基于vue的日历插件

本文转载于https://www.cnblogs.com/zwhgithub/p/8005414.html vue-calendar-component 基于 vue 2.0 开发的轻量&#xff0c;高性能日历组件占用内存小&#xff0c;性能好&#xff0c;样式好看&#xff0c;可扩展性强原生 js 开发&#xff0c;没引入第三方库 效果 Install …

实用插件(一)日历插件——My97DatePicker

注&#xff1a;My97DatePicker插件仅限pc端使用&#xff0c;若是app项目&#xff0c;建议使用ICalendar或者Mobiscroll。 &#xff08;ICalendar插件在华为手机上存在兼容性问题&#xff0c;日期不能滚动&#xff0c;但使用很简单&#xff1b;Mobiscroll使用起来较为复杂&…

sys-calendar.js带节假日的日历插件

下载地址 sys-calendar.js带节假日的日历插件&#xff0c;代码引用比较多。 dd:

jquery日历插件,可自定义日期内容

效果图&#xff1a; 使用&#xff1a; <link href"static/css/raoCalendar.css" rel"stylesheet" type"text/css"><script src"static/js/jquery.min.js"></script> <script src"static/js/raoCalendar.js…

两款超好用js日历插件(fullcalendar和zabuto_calendar)

这两款插件特别类似,其实用其中一款即可。 先展示一下我用这两款插件制作的排班系统 这个是fullcalendar插件制作的排班页面,左边新建一系列组和组员,可以将人员直接拖拽至右边的日历上,不同组以颜色区别。 这个是将上面的排班内容用zabuto_calendar插件显示出来,黄色区域…

BootStrap日历插件

BootStrap日历插件 前端引入插件三大步骤 引入插件所需的资源文件 <%--引入BootStrap日历插件相关资源文件--%><%--按照资源文件相互依赖的顺序来引入--%><script type"text/javascript" src"jquery/jquery-1.11.1-min.js"></scrip…

jQuery实现移动端手机选择日期日历插件

效果图 calendar.css html, body {color: #333;margin: 0;height: 100%;font-family: "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, Verdana, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;font-weig…

uniapp日历插件

日历插件 效果图一、使用方法二、组件编写&#xff0c;两个文件、直接上代码month.vuecalendar.vue 效果图 一、使用方法 <template><view><view class"" click"open"><text>展示日历{{value[0]}}-{{value[1]}}</text><…

calendar.js多种形式日历插件

下载地址 calendar.js是一款强大的日历插件&#xff0c;有多种形式的日历插件&#xff0c;比如&#xff0c;选择年、选择月、范围等。 dd:

日历插件:bootstrap-datetimepicker

1. 简述 前端插件使用步骤&#xff1a; 1)引入开发包&#xff1a;.js,.css 下载开发包&#xff0c;拷贝到项目webapp目录下 把开发包引入到jsp文件中&#xff1a;<link><script> 2)创建容器&#xff1a;<input type"text"…