LRU总结

article/2025/10/2 13:51:17

文章目录

    • [146. LRU 缓存机制](https://leetcode-cn.com/problems/lru-cache/)
    • ACM模式
    • LRU 在 MySQL 中的应用
    • LRU 在 Redis 中的应用
    • 面试官:来,手写一个线程安全并且可以设置过期时间的LRU缓存

146. LRU 缓存机制

力扣原题

 class Node{public int key;public int value;public Node next;public Node pre;public Node(int key,int value){this.key=key;this.value=value;}
}class DoubleList{private Node head, tail;private int size;public int size(){return size;}public DoubleList( ){head=new Node(0,0);tail=new Node(0,0);head.next=tail;tail.pre=head;size=0;}//删除public void delete(Node x){x.pre.next=x.next;x.next.pre=x.pre;size--;}//删除头部元素并返回public Node deleteFirst(){if(head.next==null){return null;}Node tem=head.next;head.next=tem.next;tem.next.pre=head;size--;return tem;}//尾部添加public void addLast(Node x){tail.pre.next=x;x.pre=tail.pre;x.next=tail;tail.pre=x;size++;}}class LRUCache {private HashMap<Integer,Node> map;private DoubleList doubleList;private int capacity;public LRUCache(int capacity) {this.capacity=capacity;map=new HashMap<>();doubleList=new DoubleList();}public int get(int key) {//先判断是否存在if(!map.containsKey(key)){return -1;}//key设置为最近使用的,先删除,再添加到链表尾部Node x=map.get(key);doubleList.delete(x);doubleList.addLast(x);return map.get(key).value;}public void put(int key, int value) {//先判断是否存在if(map.containsKey(key)){Node x=map.get(key);doubleList.delete(x);Node y=new Node(key,value);doubleList.addLast(y);map.put(key,y);return;}if(doubleList.size()==capacity){//删除链表头节点Node y=doubleList.deleteFirst();map.remove(y.key);}Node z=new Node(key,value);doubleList.addLast(z);map.put(key,z);}
}/*** Your LRUCache object will be instantiated and called as such:* LRUCache obj = new LRUCache(capacity);* int param_1 = obj.get(key);* obj.put(key,value);*/

ACM模式

import java.util.HashMap;public class LRUCache {/** Least Recent Used* 淘汰掉最不经常使用的* 使用HashMap + 双链表,实现CRUD时间复杂度为O(1)*//** 使用内部类定义一个双链表* 至于为什么是这四个成员变量呢?* LRU最大的特点,就是一个双链表的节点,同时也是HashMap的value* 那么HashMap的key从哪里来呢?* 没错,HashMap中的key和双链表节点中的key是同一个东西。* 这样说起来好像很奇怪,很啰嗦,很不优雅。然而最好,甚至必须这样做。解释如下:* 想象两个情形:* 1. 只在双链表中存value。那么——* 我们要弹出双链表最后一个元素,需要在双链表和HashMap中进行两次删除操作。* 但在HashMap中的删除操作,要借助双链表中的信息。* 废话,因为只有双链表知道谁是“最不常使用的元素”啊。* 而如果双链表中没有key,那岂不是没法在HashMap中以O(1)时间找到这个元素。* 2. 只在双链表中存key。那么——* 请问你value打算放哪里?你仿佛在搞笑哦。= =*/class DLinkedNode {int key;int value;DLinkedNode pre;DLinkedNode next;}/** 这里是成员变量,private可以去掉,没什么影响* 重要的事情再说一遍:* LRU最大的特点,就是一个双链表的节点,同时也是HashMap的value* cnt is short for count,就是目前的元素数量,作为判断。* cap is short for capacity,就是最大容量* hh和tt,是head和tail的卖萌版。虚拟头结点和尾节点。* 此时,他们是LRU数据结构的成员变量。*/private HashMap<Integer, DLinkedNode> h = new HashMap<>();private int cnt;private int cap;private DLinkedNode hh, tt;/** 初始化,没什么好讲的。*/public LRUCache(int cap) {this.cnt = 0;this.cap = cap;hh = new DLinkedNode();tt = new DLinkedNode();hh.pre = null;tt.next = null;hh.next = tt;tt.pre = hh;}/** 在某一元素被访问到的时候,先调用这个toHead()方法.* 这样,链表中从前到后的顺序,刚好就是被访问到的顺序。* 很幸运,对链表当中顺序的改动,并不影响HashMap。* 因为,改变链表的顺序,只需要改变它的前后指向,* 而不需要变动它自身的内存地址。* 何况,链表本身在内存中的存储也并不是一块连续的空间。* 这个方法本身的实现很简单:* 先让这个节点跟它的前后节点断开,然后头插。*/private void toHead(DLinkedNode node) {node.pre.next = node.next;node.next.pre = node.pre;this.push(node);}/** 头插,不解释~*/private void push(DLinkedNode node) {node.pre = this.hh;node.next = this.hh.next;this.hh.next.pre = node;this.hh.next = node;}/** 很不幸,优先级最低的节点要说再见了。* 那篇点击量最高的文章,拜托,你都没有维护链表诶!* 虽然你那样也可以运行。哈哈~*/private void popTail() {DLinkedNode tmp = tt.pre;tmp.pre.next = tt;tt.pre = tmp.pre;h.remove(tmp.key);tmp = null;}/** 通过Key获取元素,同时把这个被访问的元素设为表头的元素。* 刚刚被访问,优先级自然是最高。* 如果元素不存在,返回-1。*/public int get(int key) {DLinkedNode tmp = h.get(key);if (tmp == null) return -1;this.toHead(tmp);return tmp.value;}/** 这里就是最重要的set()方法,即“创建或修改”操作。* 一般的题目,就是要求set()和get()方法。* 也只有这个方法中,包含了缓存的容量这一参数。* 虽然前面看似是铺垫,但个人认为,前面的操作才是核心思想。* 前面的弄懂了,这里自然懂。* 实现起来也没难度,只要理顺逻辑就好。* 千万别把else写到里面那层代码里去。* 更不要自作聪明,把对cnt的操作放在前面的函数里面,把自己绕进去了。*/public void set(int key, int value) {DLinkedNode tmp = h.get(key);if (tmp == null) {tmp = new DLinkedNode();tmp.key = key;tmp.value = value;this.h.put(key, tmp);this.push(tmp);cnt ++ ;if (cnt > cap) {this.popTail();cnt -- ;}} else {tmp.value = value;this.toHead(tmp);}}/** 测试一下,输出3 2 -1。成功!*/public static void main(String[] args) {LRUCache lruCache = new LRUCache(3);lruCache.set(1, 2);lruCache.set(2, 3);lruCache.set(3, 4);System.out.println(lruCache.get(2));lruCache.set(2, 2);lruCache.set(4, 5);System.out.println(lruCache.get(2));System.out.println(lruCache.get(1));}}————————————————
版权声明:本文为CSDN博主「爆锤胖头鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/CSDNSJTU/article/details/105872082

https://blog.csdn.net/kexuanxiu1163/article/details/111877904

那么请问:为什么这里要用双链表呢,单链表为什么不行?

需要删除操作。删除一个节点不光要得到该节点本身的指针,也需要操作其前驱节点的指针,而双向链表才能支持直接查找前驱,保证操作的时间复杂度 O(1)

哈希表里面已经保存了 key ,那么链表中为什么还要存储 key 和 value 呢,只存入 value 不就行了?

当缓存容量已满,我们不仅仅要删除最后一个 Node 节点,还要把 map 中映射到该节点的 key 同时删除,而这个 key 只能由 Node 得到。如果 Node 结构中只存储 val,那么我们就无法得知 key 是什么,就无法删除 map 中的键

LRU 在 MySQL 中的应用

面试官:LRU 在 MySQL 中的应用?

LRU 在 MySQL 的应用就是 Buffer Pool,也就是缓冲池。它的目的是为了减少磁盘 IO。

缓冲池是一块连续的内存,默认大小 128M,可以进行修改。这一块连续的内存,被划分为若干默认大小为 16KB 的页。

写buffer pool的时候发现没有空闲页了,就要从Buffer pool中淘汰数据也,就根据LRU链表的数据来

InnoDB的数据页不是都是在访问的时候才缓存到BP里的,而是有一个预读机制,也就是访问某个page的数据的时候,相邻的一些page可能会很快被访问到,所以先把这些page放到BP中缓存起来。
这种预读机制又分为两种类型:一个是线程预读,InnoDB把64个相邻的page叫做一个extent(区)。如果顺序地访问了一个extent的56个page,这时候InnoDB会把这个extent缓存到BP中。第二种叫随机预读,如果BP中已经缓存的数据页个数超过13时,就会把这个extent剩余的所有page都缓存到BP中。很明显,预读把可能即将用到的数据提前加载到BP中,肯定能提升IO的性能。
但是预读也会带来一些副作用,就是导致占用的内存空间更多,剩余空闲页更少,甚至会将真正需要被缓存的热点数据挤出BP,下次访问又要去磁盘IO。那如何让热点数据不收到预读的数据的影响呢?
InnoDB将 LRU list分成两部分,靠近head的叫做new sublist,用来存放热数据,靠近tail的叫做old sublist,用来存放冷数据。中间的分割线叫做midpoint,也就是对buffer pool做一个冷热分离。

工作时,所有新数据假如到BP的时候,一律先放到冷数据区的head,所以如果有一些预读的数据没有被用到会在冷区直接被淘汰。放到LRUlist以后,如果再次被访问,都把它移动到热去的head。
如果热区的数据长时间没有被访问,会被先移动到冷区的head部,最后在tail被淘汰。

在这里插入图片描述

为了避免并发的问题,对LRU链表的操作是要加锁的。也就是每一次链表的移动,都会带来资源的竞争和等待,因此要提高效率,就要尽量减少LRU链表的移动。比如把热区非常靠近head的page移动到head。所以还有一个优化,如果一个缓存页处于热数据区,且在热数据区的前1/4,那么当访问这个页的时候,就不再将其移动到热数据区的头部。如果缓存页处于热区的后3/4,那么当访问这个页的时候,会将其移动到热区的头部。
————————————————
版权声明:本文为CSDN博主「纵横千里,捭阖四方」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xueyushenzhou/article/details/115531869

还有一个场景是全表扫描的 sql,有可能直接把整个缓冲池里面的缓冲页都换了一遍,影响其他查询语句在缓冲池的命中率。

那么怎么处理这种场景呢?

把 LRU 链表分为两截,一截里面放的是热数据,一截里面放的是冷数据。

LRU 在 Redis 中的应用

Redis 没有使用真实的 LRU 算法的原因是因为这会消耗更多的内存。

Redis 对 LRU 算法进行了改进,增加了淘汰池。通过采样一小部分键,然后在采样键中回收最适合的那个。修改下面这个参数的配置,改变采样点数量,调整算法的精度。

maxmemory-samples 5

数据库中有 3000w 的数据,而 Redis 中只有 100w 数据,如何保证 Redis 中存放的都是热点数据?

先指定淘汰策略为 allkeys-lru 或者 volatile-lru,然后再计算一下 100w 数据大概占用多少内存,根据算出来的内存,限定 Redis 占用的内存。接下来的,就交给 Redis 的淘汰策略了。

面试官:来,手写一个线程安全并且可以设置过期时间的LRU缓存


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

相关文章

html ur是什么意思_url是什么意思?

实际上,我们在使用互联网的过程中,其中有许多东西都是只会用,而不知道它到底是啥名字,看见了也不理解它是做什么的,比如今天我将和大家说的URL,实际上就是我们在互联网生活中非常常见的一个东西。 web前端学习:打造全网web前端全栈资料库(总目录)看完学的更快,掌握的…

LRU 缓存(Java)

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关键字的值&#xff0c;否则返回 -1…

LRU算法的理解,终于懂了

https://blog.csdn.net/luoweifu/article/details/8297084 以上是让我看明白的博客链接。 下面是我自己的理解&#xff0c;比如下面这道题&#xff1a; 可以这么来看&#xff0c;有一个容量为6的容器&#xff0c;每次放进去一个有编号的球&#xff0c;如果容器中有相同编号…

LRU缓存

一、什么是LRU算法 LRU&#xff0c;Least Recently Used算法&#xff0c;即一种缓存淘汰策略。 计算机的缓存容量有限&#xff0c;若缓存满了则需要删除一些内容&#xff0c;给新的缓存腾出空间&#xff0c;但问题是要删除哪些内容呢&#xff1f;当然是把用的少的缓存删掉&am…

【网络】HTTP 协议中 URI 和 URL 有什么区别?

HTTP定义 超文本传输协议&#xff08;Hyper Text Transfer Protocol&#xff0c;HTTP&#xff09;是一个简单的请求-响应协议&#xff0c;它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII形式给出 HTTP是用…

LRU缓存机制,你想知道的这里都有

概述 LRU是Least Recently Used的缩写&#xff0c;译为最近最少使用。它的理论基础为 “最近使用的数据会在未来一段时期内仍然被使用&#xff0c;已经很久没有使用的数据大概率在未来很长一段时间仍然不会被使用” 由于该思想非常契合业务场景 &#xff0c;并且可以解决很多实…

LRU简单实现-了解一下

LRU 算法 LRU 是一种作为缓存的算法&#xff0c;像 CPU 缓存&#xff0c;数据库缓存&#xff0c;浏览器缓存。以及在移动端开发时的图片安缓存&#xff0c;采用 LRU 缓存策略的应用很广泛。在面试中也是常常考察的一个点。当然也有其他缓存方法&#xff0c;常见的策略有三种&a…

数据分析:RFM模型

补充&#xff1a; RFM分析方法&#xff1a;如何对用户按价值分类 深入解读和应用RFM分析方法(模型) 深入解读RFM模型-实战应用干货 转载自&#xff1a; 接地气的陈老师|作者 接地气学堂|来源&#xff1a;https://mp.weixin.qq.com/s/00vJPb9xqx4NL5Y5cPDXsw 问他咋做数据分…

数据分析之RFM模型

一.均值 RFM模型算法 从csv文件中读取相应的数据 datapd.read_csv(./dataset.csv,encodingISO-8859-1)#读取2014年的客户信息 data_14data[data[Order-year]2014] data_142.获取相应的列 data_14 data_14[[CustomerID,OrderDate,Sales]] data_14 CustomerID为用户id OrderD…

【数据分析】基于RFM模型的线上零售中的客户细分(二):RFM模型实战

基于RFM模型的线上零售中的客户细分&#xff08;二&#xff09; 摘要&#xff1a;在上一篇博客《基于RFM模型的线上零售中的客户细分&#xff08;一&#xff09;&#xff1a;客户细分》中&#xff0c;我们了解了什么是客户细分&#xff0c;这篇博客将会结合具体的商业实例介绍同…

数据分析 一文搞懂什么是RFM模型

数据分析 | 一文搞懂什么是RFM模型 想知道你在电商平台心里的地位吗&#xff1f;学会RFM分析法&#xff0c;你自然知道 大家好&#xff0c;我是翔宇&#xff01;今天我们来了解做数据分析一定要会的分析方法之一----RFM分析法。 相信大家在前天的双十一一定也多多少少贡献了…

RFM模型原理详解与实操运用

RFM模型原理详解与实操运用 RFM模型原理介绍为什么要使用RFM模型RMF模型原理介绍RFM模型用户细分 RFM模型实例操作背景/数据介绍RFM模型异化构建代码实现 最近在 运营课程中学习了RFM模型&#xff0c;又正正好在 商务智能的课程中学习了使用K-Means聚类分析实现RFM的操作。 …

如何利用RFM分析模型进行数据分析?

RFM模型 RFM主要根据客户活跃程度和平台交易金额贡献所做的分类。 近度&#xff1a;用字母R表示&#xff0c;代表客户最近一次的活跃距离目前的天数。在这部分客户中&#xff0c;有些优质客户值得通过一定的营销手段进行激活。 频度&#xff1a;用字母F表示&#xff0c;代表…

对RFM模型的理解

客户价值可以衡量客户对企业的相对重要性&#xff0c;是企业进行差异化决策的重要标准。 由此&#xff0c;通过客户价值分类可以为企业进行差异化营销策略奠定基础。 RFM模型对客户价值分类时非常简单的一种模型 以下从几大模块说一下个人对RFM模型的理解。 1.RFM模型是什么 …

RFM分析方法

RFM分析方法 RFM分析方法RFM指标介绍RFM指标作用如何使用RFM分析方法如何精细化运营 如何给R、F、M打分-采用数据分组确定分组的范围和标准利用VLOOKUP匹配函数函数 RFM分析方法 RFM指标介绍 R:最近一次消费时间间隔&#xff08;Recency&#xff09; R越小用户价值越高F:消费…

用户行为分析模型——RFM模型

用户行为分析模型——RFM模型 1. RFM模型2. RFM模型分析应用 1. RFM模型 RFM模型根据客户活跃程度和交易金额的贡献&#xff0c;进行客户价值细分的一种方法。 R&#xff08;Recency&#xff09;——最近一次交易时间间隔。基于最近一次交易日期计算的得分&#xff0c;距离当前…

客户价值模型:RFM

文章目录 1.1、RFM 模型引入1.1.1、RFM 模型介绍1.1.1.1、一般情况下RFM模型可以说明下列几个事实&#xff1a;1.1.1.2、对最近一个月内所有用户订单数据进行统计RFM值&#xff1a; 1.1.2、RFM 模型的三个指标&#xff1a;1.1.2.1、R&#xff1a;最近一次消费&#xff08;recen…

RFM分析(Recency,Frequency,Monetary)

通过RFM方法&#xff0c;我们根据用户的属性数据分析&#xff0c;对用户进行了归类。在推送、转化等很多过程中&#xff0c;可以更加精准化&#xff0c;不至于出现用户反感的情景&#xff0c;更重要的是&#xff0c;对产品转化等商业价值也有很大的帮助。 应用背景&#xff1a;…

R语言 RFM分析

目录 一、RFM分析的定义&#xff1a; 二、RFM分析的假设 三、RFM分析的步骤 四、RMF分析实例 4.1 数据准备 4.2 计算R/F/M 4.3 将R、F、M分组打分赋值 4.4 计算RFM综合分值 4.5 客户分类 4.6 完整代码 注&#xff1a;个人学习笔记--谁说菜鸟不会数据分析 R语言篇 一、…

[数据分析] RFM分析方法

美图欣赏2022/06/08 RFM分析方法 作用:对用户分类&#xff0c;识别出有价值的用户&#xff0c;对不同价值的用户使用不同的运营决策&#xff0c;把公司有限的资源发挥到最大的效果(用于用户价值细分&#xff0c;精细化运营) RFM是3个指标的缩写:最近1次消费时间间隔(Recency)…