【深入理解LRU Cache】:缓存算法的经典之作

目录

一、什么是LRU Cache?

二、LRU Cache的实现

1.JDK中类似LRUCahe的数据结构LinkedHashMap

2.自己实现双向链表

三、LRU Cache的OJ


一、什么是LRU Cache?

LRU Cache(Least Recently Used的缩写,即最近最少使用,它是一种Cache的替换算法。看Cache替换算法这篇文章)是一种常见的缓存淘汰算法。用于在有限的缓存空间中管理数据对象。LRU Cache 的核心思想是基于时间局部性原理,即最近被访问的数据在未来可能会被再次访问。

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

注意:LRU Cache 应该更准确地归类为一种缓存淘汰算法,而非传统意义上的数据结构。尽管 LRU Cache 在实现时通常会利用数据结构(如双向链表和哈希表),但它本身更像是一种策略,用于管理缓存中的数据对象。

二、LRU Cache的实现

实现LRU Cache的方法和思路很多,但是要保持高效实现O(1)的put和get,那么使用双向链表和哈希表的搭配是最高效和经典的。

使用双向链表是因为双向链表可以实现任意位置O(1)的插入和删除,双向链表用于记录数据对象的访问顺序,每当一个数据对象被访问时,就将其移动到链表的头部。这样,链表头部的数据对象就是最近被访问的数据,而链表尾部的数据对象则是最久未被访问的数据。同时,使用哈希表能够以 O(1) 的时间复杂度进行数据对象的查找。

当缓存空间达到上限时,需要淘汰最久未被访问的数据对象。这时只需从链表尾部删除相应的数据对象,并在哈希表中删除对应的索引即可。

1.JDK中类似LRUCahe的数据结构LinkedHashMap

LinkedHashMap中有一个这样的构造方法:

重点的accessOrder:

accessOrder 是一个 boolean 类型的参数,用于指定是否按照访问顺序来排序条目。当accessOrder 被设置为 true 时,表示按照访问顺序排序条目;当 accessOrder 被设置为 false 或未指定时(默认情况下),则按照插入顺序排序条目。

示例1:当accessOrder的值为false的时候

输出结果:

{1=a, 2=b, 4=e, 3=c}
以上结果按照插入顺序进行打印。
示例2:当accessOrder的值为true的时候
输出结果:
{4=e, 3=c, 1=a, 2=b}
每次使用get方法,访问数据后,会把数据放到当前双向链表的最后。
当accessOrder为true时,get方法和put方法都会调用recordAccess方法使得最近使用的Entry移到双向链表的末尾;当accessOrder为默认值false时,从源码中可以看出recordAccess方法什么也不会做。
所以要实现一个LRU Cache,最简单的策略就是用自定义类继承LinkedHashMap,然后根据不同要求重写部分方法( 必须重写removeEldestEntry这个方法,默认是false )。
import java.util.LinkedHashMap;
import java.util.Map;public class LRUCache extends LinkedHashMap<Integer, Integer> {private final int capacity;public LRUCache(int capacity) {//accessOrder设置为false时,会按照插入顺序进行排序,当accessOrder为true时,会按照访问顺序super(capacity, 0.75f, true);this.capacity = capacity;}@Overridepublic Integer get(Object key) {//如果get不到,返回默认值-1return super.getOrDefault(key, -1);}@Overridepublic Integer put(Integer key, Integer value) {return super.put(key, value);}@Overrideprotected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {//如果集合内元素个数超过capacity,会将最不常用的元素出队,并将新元素插在尾部return size() > capacity;}
}

测试:

当然在面试时这个做法肯定不会符合面试官的要求,更好的做法是自己实现一个双向链表。

2.自己实现双向链表

前面说到:

  • 双向链表用于记录数据对象的访问顺序,每当一个数据对象被访问时,就将其移动到链表的头部。这样,链表头部的数据对象就是最近被访问的数据,而链表尾部的数据对象则是最久未被访问的数据。同时,使用哈希表能够以 O(1) 的时间复杂度进行数据对象的查找。
  • 当缓存空间达到上限时,需要淘汰最久未被访问的数据对象。这时只需从链表尾部删除相应的数据对象,并在哈希表中删除对应的索引即可。

为方便测试,我重写了toString方法,测试代码也包含在里面,根据需求删除即可。

import java.util.HashMap;
import java.util.Map;//双向链表 + 哈希表
public class MyLRUCache {//双向链表的节点类static class DLinkedNode {//键值对int key;int val;DLinkedNode next;DLinkedNode prev;public DLinkedNode() {}public DLinkedNode(int key, int val) {this.key = key;this.val = val;}}private Map<Integer, DLinkedNode> cache; //缓存private final int capacity;private int size;//虚拟的头尾节点(哨兵)private DLinkedNode head;private DLinkedNode tail;public MyLRUCache(int capacity) {this.capacity = capacity;this.cache = new HashMap<>();//连接虚拟头尾节点this.head = new DLinkedNode();this.tail = new DLinkedNode();head.next = tail;tail.prev = head;}//插入数据public void put(int key, int value) {//两种情况:元素是否存在if (!cache.containsKey(key)) {//1.元素不存在//1.1 新元素加到cache集合中DLinkedNode newNode = new DLinkedNode(key, value);cache.put(key, newNode);//1.2 插到尾部addLast(newNode);size++;//1.3 判断当前的元素个数是否超过容量if (size > capacity) {//1.4 删除第一个节点,即最近最久未使用DLinkedNode del = removeFirst();//同时需要把该元素从cache中删除cache.remove(del.key);size--;}} else {//2.元素存在,修改链表的顺序,将该节点放到尾部(最近一次使用的)DLinkedNode node = cache.get(key);//2.1 以新的值将该节点移动到尾部node.val = value;moveLast(node);}}//将节点移动到尾部private void moveLast(DLinkedNode node) {//1.删除当前节点removeNode(node);//2.尾插到链表addLast(node);}//删除节点private void removeNode(DLinkedNode node) {node.prev.next = node.next;node.next.prev = node.prev;}//删除第一个节点private DLinkedNode removeFirst() {DLinkedNode del = head.next;head.next = del.next;head.next.prev = head;return del;}//尾插到链表private void addLast(DLinkedNode node) {//连接最后一个节点和新的尾节点tail.prev.next = node;node.prev = tail.prev;//连接新尾节点和傀儡尾节点tail.prev = node;node.next = tail;}//获取数据public int get(int key) {//1.cache集合中拿到这个节点DLinkedNode node = cache.get(key);//2.判断是否有这个节点if (node != null) {//2.1 有该节点 将该节点移动到尾部(最近一次使用的)moveLast(node);return node.val;}//2.2 没有该节点,返回-1return -1;}//删除数据public boolean remove(int key) {//1.看缓存中存不存在DLinkedNode node = cache.get(key);//2.不存在,直接返回if (node == null) {return false;}//3.存在//3.1 删除链表的节点removeNode(node);//3.2 删除cache集合的数据cache.remove(key);size--;return true;}@Overridepublic String toString() {StringBuilder sbu = new StringBuilder();DLinkedNode cur = head.next;sbu.append("{");while (cur != tail) {if (cur.next != tail) {sbu.append(cur.key).append("=").append(cur.val).append(", ");} else {sbu.append(cur.key).append("=").append(cur.val);}cur = cur.next;}sbu.append("}");return sbu.toString();}public static void main(String[] args) {MyLRUCache lruCache = new MyLRUCache(3);lruCache.put(1, 10);lruCache.put(2, 11);lruCache.put(3, 12);System.out.println(lruCache);System.out.println("使用后:");lruCache.get(2);System.out.println(lruCache);lruCache.get(1);System.out.println(lruCache);lruCache.put(4, 13);System.out.println("最不常用的被删除,新元素插到尾部:");System.out.println(lruCache);}
}

三、LRU CacheOJ

LeetCode 热题100 链表专题的最后一题

146. LRU 缓存 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/lru-cache/solutions/259678/lruhuan-cun-ji-zhi-by-leetcode-solution/?envType=study-plan-v2&envId=top-100-liked前面两种实现中,MyLRUCache的get方法在获取不到数据时返回的是-1的原因就是根据这道题的要求做的,并且多写了一个remove方法。提交时将类名和构造方法改成原题默认的LRUCache,且不要main方法即可。

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

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

相关文章

跨平台是什么意思?——跟老吕学Python编程

跨平台是什么意思&#xff1f;——跟老吕学Python编程 跨平台跨平台释义跨平台软件数据库管理系统(DBMS)&#xff1a;网站服务器、应用程序服务器&#xff1a;网络浏览器&#xff1a; 跨平台编程语言跨平台详细解说跨平台应用前景 跨平台 计算机领域术语 跨平台概念是软件开发中…

Zookeeper详解

1.Zookeeper概述 1.Zookeeper概念 Zookeeper是 Apache Hadoop 项目下的一个子项目&#xff0c;是一个树形目录服务 Zookeeper 翻译过来就是动物园管理员&#xff0c;他是用来管 Hadoop&#xff08;大象&#xff09;、Hive(蜜蜂)、Pig(小猪)的管理员。简称zk Hadoop: 存储海…

canvas实现水印逻辑分析

目录 效果图一、相关文档二、分析三、实现1、将水印文字转为水印图片2、给刚生成的水印图片加入旋转以及间隔&#xff08;1&#xff09;旋转位移&#xff08;2&#xff09;间隔位移&#xff08;3&#xff09;最后使用toDataURL导出为png图片 3、将生成的水印图片依次排布在需要…

【Python数据结构与判断2/7】数据和判断小结

目录 序言 print() 变量 赋值 四种数据类型 字符串 格式化输出 四则运算 取整与取模 比较运算 逻辑运算 判断 if语句 if-else语句 if-elif-else语句 Tips 空值、0、非0非空值 实战案例 输入密码 短信模板 总结 序言 今天将对前面学过的内容进行一个复习小结…

科技引领品质:飞利浦智能锁“12年免费换新机”重塑行业新标杆

随着智能锁行业的竞争愈发火热&#xff0c;各大品牌在技术创新和服务升级方面不断推陈出新。售后服务的形态正发生深刻变化&#xff0c;从传统的保修维修到如今的技术支持、24小时在线客服等&#xff0c;各大品牌都在不断地提升售后服务水平&#xff0c;以创新的服务理念和先进…

配置与管理DNS服务器

配置与管理DNS服务器 **1&#xff0c;什么是DNS&#xff1f;**负责将域名转换成实际想对应的ip地址&#xff0c;这个过程交域名解析。 **2&#xff0c;域名解析的方法&#xff1a;**分布式&#xff0c;层次结构的数据库系统。根域&#xff0c;顶级域&#xff0c;二级域&#…

sql注入基础学习

1.常用SQL语句 01、显示数据库 show databases&#xff1b; 02、打开数据库 use db name&#xff1b; 03、显示数据表 show tables&#xff1b; 04、显示表结构 describe table_name&#xff1b; 05、显示表中各字段信息&#xff0c;即表结构 show columns from table_nam…

面向对象的编程语言是什么意思?——跟老吕学Python编程

面向对象的编程语言是什么意思&#xff1f;——跟老吕学Python编程 面向对象是什么意思&#xff1f;面向对象的定义面向对象的早期发展面向对象的背景1.审视问题域的视角2.抽象级别3.封装体4.可重用性 面向对象的特征面向对象的开发方法面向对象程序设计基本思想实现 面向对象的…

数据结构-稀疏数组

文章目录 1、什么是稀疏数组&#xff1f;2、稀疏数组的存储流程3、代码实现4、运行结果 1、什么是稀疏数组&#xff1f; 当一个数组中大部分元素为0&#xff0c;或者为同一个值的数组时&#xff0c;可以使用稀疏数组来保存该数组。 2、稀疏数组的存储流程 记录数组一共有几行…

活体检测(点头,摇头,张嘴等动态识别)

活体检测&#xff08;点头&#xff0c;摇头&#xff0c;张嘴等动态识别&#xff09; 某本书里有一句话&#xff0c;等我去读、去拍案。 田间的野老&#xff0c;等我去了解、去惊识。 山风与发&#xff0c;冷泉与舌&#xff0c; 流云与眼&#xff0c;松涛与耳&#xff0c; 他们等…

21、状态模式(行为性模式)

版本一、get状态指针 #include <iostream> using namespace std;//前置声明 class Context;//状态 class State{ public://4个状态virtual void toUp (Context& context){ }virtual void toDown (Context& context){ }virtual void toLeft (Context& cont…

架构学习总结:企业架构=业务+数据+技术+应用架构

最近再次研读DAMA数据管理知识体系,结合工作对什么是企业架构?如何开展企业架构设计工作有一些新的认识,供大家参考。企业架构包括企业的业务架构、数据架构、技术架构和应用架构,要想做好企业的信息化数字化建设规划,这四个架构都不可缺少,这四个方面的内容共同组成了企…

[云原生] k8s配置资源管理

一、Secret的资源配置 1.1 Secret配置的相关说明 Secret 是用来保存密码、token、密钥等敏感数据的 k8s 资源&#xff0c;这类数据虽然也可以存放在 Pod 或者镜像中&#xff0c;但是放在 Secret 中是为了更方便的控制如何使用数据&#xff0c;并减少暴露的风险。 Secret 有…

【JavaSE】抽象类与接口

Object 类 类 java.lang.Object是类层次结构的根类&#xff0c;即所有类的父类。 除Object类之外的任何一个Java类&#xff0c;全部直接或间接的继承于Object类。由此&#xff0c;Object类也被称为根父类。Object类中声明的成员具有通用性&#xff0c;并且Object类中没有声明…

300W-500W-700W-1000W超薄制动电阻

EAK制动电阻&#xff0c;最大连续功率&#xff1a;300 W--1000W 制动电阻器&#xff0c;用于带有中低功率变频器 或作为充电电阻器的驱动器。 安装在变频器附近。 防护等级 IP 20 / IP 54 可根据要求提供更高的防护等级 测试电压 2.5 kV AC 可根据要求提供其他容量和安装…

SMART PLC自适应低通滤波器(收放卷线速度滤波)

一阶低通滤波器更多内容请参考信号处理专栏相关文章,常用链接如下: 1、SMART PLC 低通滤波器和模拟量采集应用 https://rxxw-control.blog.csdn.net/article/details/136595982https://rxxw-control.blog.csdn.net/article/details/1365959822、SMART PLC双线性变换和后向差…

Docker笔记-进入运行中的镜像,查看日志等操作

docker搭建好后&#xff0c;查看运行的docker镜像&#xff1a; docker ps -a 进入运行的容器&#xff0c;命令如下&#xff1a; docker exec -it <容器ID> /bin/bash # 或者&#xff0c;直接用容器里面的命令&#xff0c;比如mysql镜像 docker exec -it <容器ID>…

网络协议常见问题

网络协议常见问题 OSI&#xff08;Open Systems Interconnection&#xff09;模型OSI 封装 TCP/IP协议栈IP数据报的报头TCP头格式UDP头格式TCP (3-way shake)三次握手建立连接&#xff1a;为什么三次握手才可以初始化 Socket、序列号和窗口大小并建立 TCP 连接。每次建立TCP连接…

蓝桥杯单片机---第十二届省赛题目解析

文章目录 比赛题目一、代码相关定义、声明1.头文件声明2.变量声明 二、主要函数1.main函数2.按键扫描3.数码管显示4.电压模式1、2输出 & LED显示5.定时器中断6.消除85C显示 三、次要函数1.初始化函数Init2.按键函数Key3.LED函数Led4.数码管函数Seg5.iic函数中6.onewire函数…

【LeetCode】17.电话号码的字母组合

题目 链接&#xff1a;17. 电话号码的字母组合 - 力扣&#xff08;LeetCode&#xff09; 给定一个仅包含数字2-9的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按任意顺序返回 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何…