LRU算法介绍和在JAVA的实现及源码分析
一、写随笔的原因:最近准备去朋友公司面试,他说让我看一下LRU算法,就此整理一下,方便以后的复习。
二、具体的内容:
1.简介:
LRU是Least Recently Used的缩写,即最近最少使用。算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小”。
这里不得不说i一下LFU(Least Frequently Used),其核心思想是“如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小”
LRU和LFU算法的区别是概括来说,LRU强调的是访问时间,而LFU则强调的是访问次数。
2.使用场景:
一般来说它是用来作为一种缓存淘汰算法。
3.实现方法:
方法一:用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。该方法有个致命的缺点就是需要不停地维护数据项的访问时间戳,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。
方法二:可以使用链表来实现,分三步走:首先当新数据插入的时候,依次按顺序放到链表中; 其次查询数据时,已经存在链表中时(即缓存数据被访问),则将数据移到链表尾部;当链表满的时候,将链表顶部的数据丢弃。
4.Java中的实现代码:
Java中最简单的LRU算法实现,就是利用LinkedHashMap,覆写其中的removeEldestEntry(Map.Entry)方法即可,内部就是使用的方法二实现的。代码如下:
import java.util.LinkedHashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUTest { static class LRULinkedHashMap<K,V> extends LinkedHashMap<K,V> {
//定义缓存的容量
private int capacity;
//带参数的构造器
LRULinkedHashMap(int capacity){
//如果accessOrder为true的话,则会把访问过的元素放在链表后面,放置顺序是访问的顺序(LinkedHashMap里面的get方法,当accessOrder为true,会走afterNodeAccess方法将节点移到最后)
//如果accessOrder为flase的话,则按插入顺序来遍历
super(16,0.75f,true);
//传入指定的缓存最大容量
this.capacity=capacity;
}
//实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素
@Override
public boolean removeEldestEntry(Map.Entry<K, V> eldest){
return size()>capacity;
}
}
//test
public static void main(String[] args) {
LRULinkedHashMap<String, Integer> testCache = new LRULinkedHashMap<>(3);
testCache.put("A", 1);
testCache.put("B", 2);
testCache.put("C", 3);
System.out.println(testCache.get("B"));
System.out.println(testCache.get("A"));
testCache.put("D", 4);
System.out.println(testCache.get("D"));
System.out.println(testCache.get("C"));
}
}
输出结果:
为何上述简单的代码LRU算法,具体要看LinkedHashMap里的put和get方法的源代码。
1.首先是put()方法,这个在LinkedHashMap并没有重写,直接使用的是HashMap中的put()方法,这里解释一下上面的重写的removeEldestEntry()方法,在上次HashMap原理探究中,put()方法会调用内部的putVal()方法,putVal()方法中最后会调用一个afterNodeInsertion(evict);这个方法在LinkedHashMap里面有实现:
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first; //头结点
if (evict && (first = head) != null && removeEldestEntry(first)) { //会调用我们重写的removeEldestEntry()
K key = first.key;
removeNode(hash(key), key, null, false, true); // 满足条件,这移除头结点
}
}
会调用我们重写的removeEldestEntry()方法,当返回为true时,则会removeNode()方法移除链表的顶端元素,满足方法二中的第三个步骤。
2.接下来看一下get()方法,这个在LinkedHashMap有重写,代码如下:
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
这里会用到我们初始化时设置的accessOrder的值,我们当时设置的是ture,也就意味着会调用afterNodeAccess()方法,我们继续看afterNodeAccess的源码:
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
源码中有块英文注释// move node to last,很明显是将查到的数据移动到链表的尾部。满足方法二中的第二个步骤。
至于方法二中的第一个步骤,LinkedHashMap本身就是有顺序的插入,顺带分析下为啥它是如何实现有顺序插入的。由于它并没有单独实现put()方法,所以要看HashMap中的put()实现,还是参照上次HashMap原理探究 ,在put()方法会调用内部的putVal()方法,putVal()方法中添加新节点会调用newNode()方法,而LinkedHashMap对newNode进行了重写,代码如下:
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p); // 这个方法就是将新节点顺序的插入到为节点的后面
return p;
}
linkNodeLast()方法:
// link at the end of list
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
三、总结:
本篇随笔主要就是LRU的介绍和在java中的快速实现,实现的代码相信网上非常的多,主要还是从源码角度分析了为何可以这样简单的实现,让我们能够更加理解LRU的基于链表的实现吧,同时也分析了下LinkedHashMap的部分源码实现,让大家能够更深入的理解吧。
LRU算法介绍和在JAVA的实现及源码分析的更多相关文章
- 死磕 java集合之PriorityBlockingQueue源码分析
问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...
- 死磕 java集合之LinkedHashSet源码分析
问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...
- 死磕 java集合之ArrayDeque源码分析
问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...
- Java并发指南10:Java 读写锁 ReentrantReadWriteLock 源码分析
Java 读写锁 ReentrantReadWriteLock 源码分析 转自:https://www.javadoop.com/post/reentrant-read-write-lock#toc5 ...
- 【死磕 Java 集合】— ConcurrentSkipListMap源码分析
转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...
- java线程池ThreadPoolExector源码分析
java线程池ThreadPoolExector源码分析 今天研究了下ThreadPoolExector源码,大致上总结了以下几点跟大家分享下: 一.ThreadPoolExector几个主要变量 先 ...
- 死磕 java集合之DelayQueue源码分析
问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...
- 死磕 java集合之PriorityQueue源码分析
问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...
- 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计
问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...
随机推荐
- 品牌logo在EDM数据营销中的运用
作为让客户识别并记住的“门面”, 品牌LOGO的设计与宣导一直都是品牌化输出的重中之重 . 而在邮件营销中,为了让用户在浏览邮件内容前先有品牌概念,营销人员在放置LOGO时常常仅把显眼突出纳入考虑因素 ...
- 阶段3 3.SpringMVC·_06.异常处理及拦截器_6 SpringMVC拦截器之拦截器入门代码
创建拦截器 新建包 实现拦截器的接口 接口中没有强制实现里面的方法.jdk1.8的特性.接口中已经实现了方法 这就是相当于实现了这个接口.方法已经全帮你实现过了. 如果想去写新的实现方法.Ctrl+o ...
- 生成html报告
jmeter -g "D:\JMeter WorkSpace\JMeterScript\IAC\Aggregate Graph\AggregateGraph.csv" -o &qu ...
- Azure sql database 监控存储过程的传参情况
背景 实施开发的同事找到我,反馈说项目中使用Azure sql database 之后,无法使用Profiler来监控自己开发的存储过程的参数传参情况.确实profiler这些实例级别的工具在Azur ...
- 【SVN】导出项目后报错汇总
原文链接 1.jsp页面内:标点符号,引入报错 解决方法:关闭此项目的jsp验证,右键,最下面一个,Verification,右边一溜只留一个dtd就好 2. 编码问题-乱码 刚拉下来的项目编码可能与 ...
- java:Echarts,POI
1.Echarts: demo.js: function demo(selector){ var myEcharts=echarts.init(selector); var option = { ti ...
- 【VS开发】PCIe体系结构的组成部件
PCIe总线作为处理器系统的局部总线,其作用与PCI总线类似,主要目的是为了连接处理器系统中的外部设备,当然PCIe总线也可以连接其他处理器系统.在不同的处理器系统中,PCIe体系结构的实现方法略有不 ...
- zip 命令
NAME zip - package and compress (archive) files SYNOPSIS zip [-aABcdDeEfFghjklLmoqrRSTuvVwXy ...
- 【Linux-驱动】printk的打印级别
级别: 日志级别用宏表示,日志级别宏展开为一个字符串,在编译是由预处理器将它和消息本文拼接成一个字符串,因此printk函数中日志级别宏和格式字符串间不能有逗号.printk的日志级别定义在 /inc ...
- vue---EleElement UI 表格导出功能
步骤一:安装依赖 安装依赖:npm install --save xlsx file-saver 步骤二:在放置需要导出功能的组件中引入相关组件 import FileSaver from 'file ...