什么是LRU算法

article/2025/9/29 18:25:23

什么是LRU

LRU 英文全称(Least recently used,最近最少使用)属于典型的内存管理算法。
内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。用通俗的话来说就是最近被频繁访问的数据会具备更高的留存,淘汰那些不常被访问的数据。 

LRU算法又叫淘汰算法,根据数据历史访问记录进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

LRU: Least Recently Used, 最近最少使用,主要应用场景是缓存,缓存规则如下。
①.最近被使用或访问的数据放置在最前面;
②.没当缓存命中(即缓存数据被访问)则将数据移到头部;
③.当缓存数量达到最大值时,将最近最少访问的数据剔除;   

LRU应用场景

①.vue 中 keep-alive 内置组件,组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验。 
②.底层的内存管理,页面置换算法。
③.一般的缓存服务,memcache\redis之类。

虚拟内存

关于操作系统的内存管理,如何节省利用容量不大的内存为最多的进程提供资源,一直是研究的重要方向。而内存的虚拟存储管理,是现在最通用,最成功的方式—— 在内存有限的情况下,扩展一部分外存作为虚拟内存,真正的内存只存储当前运行时所用得到信息。这无疑极大地扩充了内存的功能,极大地提高了计算机的并发度。虚拟页式存储管理,则是将进程所需空间划分为多个页面,内存中只存放当前所需页面,其余页面放入外存的管理方式。 

然而,有利就有弊,虚拟页式存储管理减少了进程所需的内存空间,却也带来了运行时间变长这一缺点:进程运行过程中,不可避免地要把在外存中存放的一些信息和内存中已有的进行交换,由于外存的低速,这一步骤所花费的时间不可忽略。因而,采取尽量好的算法以减少读取外存的次数。

缓存

缓存是一种提高数据读取性能的技术,在硬件设计,软件开发中都有非常广泛的作用,常见的CPU缓存,数据库缓存,浏览器缓存等。
缓存大小是有限的,当缓存被用满时,哪些数据应该被清理出去,哪些数据应该被保留?这就需要缓存淘汰策略来决定。

常见的策略有三种:
先进先出策略(FIFO,First In,First Out)
最少使用策略(LFU,Least Rrequently Used)
最近最少使用策略(LRU,Least Recently Used)

数组与链表

从底层的存储结构看,数组需要一块连续的内存空间来存储,对内存的要求比较高。如果我们申请一个100MB大小的数组,当内存中没有连续的,足够大的存储空间,即便内存的剩余可用空间大于100MB,仍然会申请失败!

而链表不一样,它并不需要一块连续的内存空间,它通过指针将一组零散的内存块串联起来使用,所以我们如果申请的是100MB大小的链表,是不会出现问题的!

数组和链表性能对比 

数组和链表是两种不同的数据结构。它们的内存存储方式有区别,所以在插入、删除、访问操作的时间复杂度是不一样的,如下所示: 

数组和链表的对比,并不能局限于时间复杂度,在实际的程序开发中,不能仅仅利用复杂度分析就决定使用哪个数据结构来存储数据。

数组简单易用,在实现上使用的是连续的内存空间,可以借助CPU的缓存机制,预读数组中的数据,所以访问效率更高。而链表在内存中并不是连续存储,所以对CPU缓存不是很好,不能有效的预读。

数组的缺点是大小固定,一经声明就要占用整块连续内存空间。如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,导致“内存不足(out of memory)”;如果声明的数组过小,则可能出现不够用的情况,这时只能再申请一个更大的内存空间,把原数组拷贝进去,非常耗时。而链表没有大小的限制,支持动态扩容,这也是它与数组最大的区别。

如果你的程序对内存使用比较苛刻,则可以考虑选择使用数组,因为链表中的每个结点都需要额外的存储空间去存储指向下一个结点的指针,所以链表的内存消耗会大点,而且对链表进行频繁的插入、删除操作会导致频繁的内存申请和释放,容易造成内存碎片,如果是Go语言,就可能会导致频繁的GC。所以,在我们实际的开发中,要根据具体的开发场景,来权衡选择使用哪种数据结构。

FIFO和LRU

FIFO是最简单的一种缓存算法,设置缓存上限,当达到了缓存上限的时候,按照先进先出的策略进行淘汰,再增加进新的 k-v 。

使用了一个对象作为缓存,一个数组配合着记录添加进对象时的顺序,判断是否到达上限,若到达上限取数组中的第一个元素key,对应删除对象中的键值。

/*** FIFO队列算法实现缓存* 需要一个对象和一个数组作为辅助* 数组记录进入顺序*/
class FifoCache{constructor(limit){this.limit = limit || 10this.map = {}this.keys = []}set(key,value){let map = this.maplet keys = this.keysif (!Object.prototype.hasOwnProperty.call(map,key)) {if (keys.length === this.limit) {delete map[keys.shift()]//先进先出,删除队列第一个元素}keys.push(key)}map[key] = value//无论存在与否都对map中的key赋值}get(key){return this.map[key]}
}module.exports = FifoCache

LRU算法的观点是,最近被访问的数据那么它将来访问的概率就大,缓存满的时候,优先淘汰最无人问津者

算法实现思路:基于一个双链表的数据结构,在没有满员的情况下,新来的 k-v 放在链表的头部,以后每次获取缓存中的 k-v 时就将该k-v移到最前面,缓存满的时候优先淘汰末尾的。

双向链表的特点,具有头尾指针,每个节点都有 prev(前驱) 和 next(后继) 指针分别指向他的前一个和后一个节点。

关键点:在双链表的插入过程中要注意顺序问题,一定是在保持链表不断的情况下先处理指针,最后才将原头指针指向新插入的元素。

class LruCache {constructor(limit) {this.limit = limit || 10//head 指针指向表头元素,即为最常用的元素this.head = this.tail = undefinedthis.map = {}this.size = 0}get(key, IfreturnNode) {let node = this.map[key]// 如果查找不到含有`key`这个属性的缓存对象if (node === undefined) return// 如果查找到的缓存对象已经是 tail (最近使用过的)if (node === this.head) { //判断该节点是不是是第一个节点// 是的话,皆大欢喜,不用移动元素,直接返回return returnnode ?node :node.value}// 不是头结点,铁定要移动元素了if (node.prev) { //首先要判断该节点是不是有前驱if (node === this.tail) { //有前驱,若是尾节点的话多一步,让尾指针指向当前节点的前驱this.tail = node.prev}//把当前节点的后继交接给当前节点的前驱去指向。node.prev.next = node.next}if (node.next) { //判断该节点是不是有后继//有后继的话直接让后继的前驱指向当前节点的前驱node.next.prev = node.prev//整个一个过程就是把当前节点拿出来,并且保证链表不断,下面开始移动当前节点了}node.prev = undefined //移动到最前面,所以没了前驱node.next = this.head //注意!!! 这里要先把之前的排头给接到手!!!!让当前节点的后继指向原排头if (this.head) {this.head.prev = node //让之前的排头的前驱指向现在的节点}this.head = node //完成了交接,才能执行此步!不然就找不到之前的排头啦!return IfreturnNode ?node :node.value}set(key, value) {// 之前的算法可以直接存k-v但是现在要把简单的 k-v 封装成一个满足双链表的节点//1.查看是否已经有了该节点let node = this.get(key, true)if (!node) {if (this.size === this.limit) { //判断缓存是否达到上限//达到了,要删最后一个节点了。if (this.tail) {this.tail = this.tail.prevthis.tail.prev.next = undefined//平滑断链之后,销毁当前节点this.tail.prev = this.tail.next = undefinedthis.map[this.tail.key] = undefined//当前缓存内存释放一个槽位this.size--}node = {key: key}this.map[key] = nodeif(this.head){//判断缓存里面是不是有节点this.head.prev = nodenode.next = this.head}else{//缓存里没有值,皆大欢喜,直接让head指向新节点就行了this.head = nodethis.tail = node}this.size++//减少一个缓存槽位}}//节点存不存在都要给他重新赋值啊node.value = value}
}module.exports = LruCache


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

相关文章

LRU缓存实现与原理

概念 LRU是 Least Recently Used 的缩写,即最近最少使用页面置换算法,是为虚拟页式存储管理服务的,是根据页面调入内存后的使用情况进行决策了。由于无法预测各页面将来的使用情况,只能利用“最近的过去”作为“最近的将来”的近似…

LRU算法的详细介绍与实现

1.背景 LRU(least recently used-最近最少使用算法),是一种内存数据淘汰策略,使用常见是当内存不足时,需要淘汰最近最少使用的数据。LRU常用语缓存系统的淘汰策略。 2.LRU原理 LRU最早实在操作系统接触到这个算法的…

LRU原来如此简单

文章目录 前言一、LRU是什么?二、LFU是什么?三、LRU和LFU的比较四、LFU代码实现(看懂LFU就自然懂了LRU了)1、LFU类2、Node类3、测试 写在最后,感谢点赞关注收藏转发 前言 现在缓存技术在项目中随处可见,但…

LRU算法详解

概念理解 1.LRU是Least Recently Used的缩写,即最近最少使用页面置换算法,是为虚拟页式存储管理服务的,是根据页面调入内存后的使用情况进行决策了。由于无法预测各页面将来的使用情况,只能利用“最近的过去”作为“最近的将来”的…

LRU算法

1.什么是LRU算法 LRU算法又称最近最少使用算法,它的基本思想是长期不被使用的数据,在未来被用到的几率也不大,所以当新的数据进来时我们可以优先把这些数据替换掉。 在LRU算法中,使用了一种有趣的数据结构,称为哈希链…

巧用 NGINX 实现大规模分布式集群的高可用性

原文作者:陶辉 原文链接:巧用 NGINX 实现大规模分布式集群的高可用性 - NGINX开源社区 转载来源:NGINX开源社区 本文是我对2019年GOPS深圳站演讲的文字整理。这里我希望带给各位读者的是,如何站在整个互联网背景下系统化地理解Ngi…

朱邦复

朱邦复 求助编辑百科名片 朱邦复,仓颉输入法的发明人,现任香港上市公司文化传信集团的副主席。湖北省黄冈县人。为中文终端机、仓颉输入法、汉卡的发明人。由于其对中文电脑发展的众多贡献,台湾及香港地区的华人誉其为“中文电脑之父”、“中…

TCP/IP的底层队列实现原理

个人博客请访问 http://www.x0100.top 自从上次学习了TCP/IP的拥塞控制算法后,我越发想要更加深入的了解TCP/IP的一些底层原理,搜索了很多网络上的资料,看到了陶辉大神关于高性能网络编程的专栏,收益颇多。今天就总结一下&#…

从码农到工程师:看一下这6点!

作者:陶辉笔记来源:http://www.taohui.pub 许多程序员自称码农,因为每天事情总也做不完,而这些工作也没有给自己带来职业上的提升,总在原地打转,自己的工作似乎随时可被新人替换,可有可无。于是…

Nginx五大类变量详解

Nginx变量详解 文章目录 Nginx变量详解一、HTTP请求相关的变量二、TCP连接相关的变量三、Nginx处理请求过程中产生的变量四、发送HTTP响应时相关的变量五、Nginx系统变量 为了方便记忆呢,我把nginx的全部变量分为5种,详情见下图 本文内容取自极客时间陶辉…

开复博士见面会

CSDN的CTO俱乐部成立一年多来,做过很多次活动了。我只参加了两次,第二次就是开复博士的创新工厂介绍会。我对创新工厂兴趣并不大,但很想近距离接触一下这位声名远播的演讲者和布道者。 曾经和一个VP的会议上&#xff…

Nginx核心知识100讲学习笔记(陶辉)Nginx架构基础(一)

(转载,非常不错的文章) 一、Nginx的请求处理流程进程结构 1、Nginx的请求处理流程 2、Nginx的进程结构 3、进程作用 1、Master进程 1、是进行work进程的监控管理的2、看看work进程是否正常工作需不需要进行热部署、需不需要重新载入配置文件 2、Cache manager …

在这里,NGINX 创始人 Igor Sysoev 将亲述 NGINX 的诞生史

2020 年 5 月 20 日,一场 NGINX 在国内的盛会、一个所有 NGINX 用户 & 爱好者朝圣的最佳场所,F5 线上技术峰会 – NGINX 专场将以线上直播的形式面向所有开发者召开。届时各位 NGINX 开发者心目中的偶像 NGINX 创始人 Igor Sysoev 以及国内 NGINX 技…

如何用NGINX实现UDP四层反向代理?

原文作者:陶辉 原文链接:如何用NGINX实现UDP四层反向代理?- NGINX开源社区 转载来源:NGINX开源社区 在实时性要求较高的特殊场景下,简单的UDP协议仍然是我们的主要手段。 UDP协议没有重传机制,还适用于同时…

Nginx深度剖析,真是被大牛讲透了

开篇 Nginx是一款非常出色的服务器软件,从开始工作到现在,周围所有的公司都在使用Nginx。在多年的使用过程中,逐渐对Nginx的源码产生了浓厚的兴趣,我不满足于仅仅会使用,我想更加深入的理解它的内部工作原理。只有深入…

CPGIS三十周年专访系列|陶闯主席

近日,国际华人地理信息科学协会CPGIS建会30周年之际,开展了一次历届主席专访活动,陶闯博士作为国际华人地理信息科学协会CPGIS,2000-2001届的主席,接受了CPGIS的专访,忆往昔,看今朝,…

【10道大厂必考的计算机网络问题】陶辉老师

目录 1.请详细介绍一下TCP的三次握手协议,为什么要三次握手? 2.说说HTTP协议中缓存的处理流程?缓存的应用流程?与缓存相关的HTTP头部? 3.在地址栏键入URL后,网络世界发生了什么? 4.使用HTTP长连接有哪些优点&#…

HTTP详细介绍

转自:HTTP详细介绍本文参考 wiki百科、陶辉老师《Web协议详解与抓包实战》 和 作者在一线多年的运维工作总结希望对大家有所帮助。常见osi模型(7层)发起组织: 国际电信联盟电信标准化部门,与国际标准组织(ISOhttps://www.pinlue.c…

10 道大厂面试必考的计算机网络问题-陶辉 极客时间

大厂中更多会考察你的长板. 在大厂中要学会求助 1.TCP的三次握手机制,为什么要三次? 为什么需要握手? 需要同步序列号,当然也有MSS(最大报文段长度),滑动窗口. 为什么是3次? 正常想法应该是: C:我要建连接,我的seq是这个; S:我收到了 S:我的Seq是这个 C:我也收到了…

TVP思享 | 四个全新维度,极限优化HTTP性能

导语 | 当产品的用户量不断翻番时,需求会倒逼着你优化HTTP协议。那么,要想极限优化HTTP性能,应该从哪些维度出发呢?本文将由TVP陶辉老师,为大家分享四个全新维度。「TVP思享」专栏,凝结大咖思考&#xff0c…