LRU缓存淘汰算法分析与实现
概述
记录一下LRU缓存淘汰算法的实现。
原理
LRU(Least recently used,最近最少使用)缓存算法根据数据最近被访问的情况来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
介绍
下图中,介绍了一个缓存空间为5的缓存队列,当访问数据的顺序是:1,2,3,4,5,6,7,6,4,0时空间中数据的变化过程。
可以发现:
- 当缓存空间未满时,数据一直往新的空间写;
- 当缓存满,并且缓存中没有需要访问的数据时,最先进入缓存的数据被淘汰掉;
- 当缓存满,并且缓存中有需要访问的数据时,做了一个数据交换,把访问的数据拿出来,其余数据往下压,最后把访问的数据放到顶部
在这里,可能有疑问,就是把“数据交换”于“数据完全新增和删除”有什么区别呢?答案是性能,前者是移动指针,后者是更新整个内存空间,后者所花费的系统开销远比前者大得多。
实现
看了算法的介绍,我们想到的数据结构就是链表了。
- 双向链表的数据结构
/** * 双向链表数据结构 */ private class NodePair{ NodePair frontNode; NodePair postNode; int data; }
- 逆序查找链表
/** * 根据数据逆序查找链表中是否有此节点,有,则把该点提出来,放到current的位置 * 当匹配到的时候,返回true * @param data */ public boolean searchNode(int data){ boolean flag = false; NodePair tempNode = current; //不匹配,即没找到,则继续查找 while(tempNode.frontNode != null || tempNode.data != data){ tempNode = tempNode.frontNode; } //这个判读表示匹配到了 if(tempNode.data == data){ tempNode.frontNode.postNode = tempNode.postNode; tempNode.postNode.frontNode = tempNode.frontNode; current = tempNode; flag = true; } return flag; }
- 空间满了,并且缓存中没有待访问的数据,删除最下面的节点,再新增一个节点,相当于重新赋值最下面的节点,如图
红线表示,head将要指向倒数第二个点了,即,倒数第二个点要变成现在最底下的点了。
/** * 给head节点重新赋值操作 * 实现细节是: * 0.倒数第二个点(head的下一个点)的frontNode引用指向null * 1.给head所指节点重新赋值 * 2.current节点的frontNode引用指向head * 3.把current节点指向head * 4.把head指向head的下一个节点(即,倒数第二个点) */ public void resetHeadNode(int data){ NodePair secondNode = head.postNode; head.postNode.frontNode = null; head.data = data; head.frontNode = current; head.postNode = null; current.postNode = head; current = head; head = secondNode; }
- 缓存满了,查找缓存中是否有待访问数据,有的话,同时把有的数据放到current指针所指位置。
/** * 根据数据逆序查找链表中是否有此节点,有,则把该点提出来,放到current的位置 * 当匹配到的时候,返回true * @param data */ public boolean searchNode(int data){ boolean flag = false; NodePair tempNode = current; //不匹配,即没找到,则继续查找 while(tempNode.frontNode != null || tempNode.data != data){ tempNode = tempNode.frontNode; } //这个判读表示匹配到了 if(tempNode.data == data){ tempNode.frontNode.postNode = tempNode.postNode; tempNode.postNode.frontNode = tempNode.frontNode; current = tempNode; flag = true; } return flag; }
- 新增节点
/** * 往LRU缓存中插入数据 * @param data */ public void addNode(int data){ //缓存未满,不需要删除,直接插入 if(length <= size){ NodePair tempNode = new NodePair(); tempNode.frontNode = current; tempNode.postNode = null; tempNode.data = data; current = tempNode; length++; } //缓存满了,查找缓存中有没有数据 else{ if(!searchNode(data)){ //缓存中没有,需要给head节点重新赋值 resetHeadNode(data); } } }
LRU算法的缺点
如果有几个不符合“如果数据最近被访问过,那么将来被访问的几率也更高”的规律时,会破坏缓存,导致性能下降。
总结
写算法时,通过画图,写步骤,先产生一个清晰的思路,然后一步步去做实现刚才思考的步骤。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
LinkedBlockingQueue阻塞队列offer()操作抛出中断异常
说明 在使用LinkedBlockingQueue的offer方法时,出现了中断异常,现分析一下出现这个中断异常的原因。 会产生中断异常的Demo import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; public class TestLinkedBlockingQueue { public static void main(String[] args) { LinkedBlockingQueue lbq = new LinkedBlockingQueue(3); for(int i = 0; i < 10; i++){ try { System.out.println(lbq); //这里用offer方法往阻塞队列里面添加对象,此方法表示若队列满了,则等待1秒,1秒后若队列还是满的,则丢弃数据。 lbq.offer(i, 1000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // TO...
- 下一篇
CompletableFuture的exceptionally
CompletableFuture的exceptionally 代码: private void test() { System.out.println("开始..."); CompletableFuture.supplyAsync(new Supplier<String>() { @Override public String get() { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } // 此处故意抛出一个空指针异常。 // 导致代码处理逻辑转入到exceptionally(new Function<Throwable, String>() if (true) { throw new NullPointerException(); } System.out.println("返回 zhang"); return "zhang"; } }).exceptionally(new Function<Throwable, S...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Mario游戏-低调大师作品
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8编译安装MySQL8.0.19
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7