java lru lfu_Java集合之LinkedHashMap实现LRU,LFU,FIFO算法
LinkedHashMap=双向链表+HashMap,存储相比HashMap会多了一个前节点,后节点.
LinkedHashMap简介
LinkedHashMap主要是通过HashMap+双向链表来实现的,主要作用目前就个人使用来看,可以主要有插入排序,访问排序.有了这两个排序就可以很简单的实现LRU,FIFO缓存算法.至于怎么用,就看个人的改造能力了.下面将会分析源码,以及用它来实现各种缓存算法.
LinkedHashMap源码分析
在jdk1.8的HashMap中多了三个方法里面什么都没做,为的就是留给LinkedHashMap去实现
void afterNodeAccess(Nodep) { }节点访问后:重复插入相同的key值或者get过该key,都会将该元素移动至队列尾部
void afterNodeInsertion(boolean evict) { }节点插入后:节点插入后做什么操作呢?队列满的时候删除队列头部的元素
void afterNodeRemoval(Nodep) { }节点移除后:移除节点后操作
这三个方法实在HashMap中定义的,但是具体的实现是在LinkedHashMap中实现的,这也是LinkedHashMap的意义所在.
源码分析
public class LinkedHashMap
extends HashMap
implements Map{
//在HashMap的节点上包装了一个前节点,尾节点
static class Entry extends HashMap.Node {
Entry before, after;
Entry(int hash, K key, V value, Node next) {
super(hash, key, value, next);
}
}
//Linked是一个双向链表的,这个是链表头节点,下面是尾节点
transient LinkedHashMap.Entry head;
transient LinkedHashMap.Entry tail;
// true:按访问顺序存储(LRU),false:按存入顺序存储(FIFO,先进先出)
final boolean accessOrder;
}
调用put方法时,其实调用的是HashMap的方法,HashMap在newNode时,LinkeHashMap重写了该方法.
Node newNode(int hash, K key, V value, Node e) {
LinkedHashMap.Entry p =
new LinkedHashMap.Entry(hash, key, value, e);
linkNodeLast(p); //将该节点拼接为一个linkNode
return p;//返回node节点
}
//这里就是组装一个双向链表哦.
private void linkNodeLast(LinkedHashMap.Entry p) {
LinkedHashMap.Entry last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
从上面可以看出同一个node,被用来存储到HashMap中,又被用来组成一个链表.相当于双重数据结构.并不意味着数据就是两份,要知道node只有一个哦,Java里面通过引用多个值引用同一个对象.就如:
Object o=new Object();
Object a=o;
Object b=o;
这里始终只有一个对象.他们三个都引用了o这个对象.
afterNodeInsertion
在HashMap的put方法最后有这么一句:afterNodeInsertion(evict);LinkedHashMap重写了之后
void afterNodeInsertion(boolean evict) { // 是否移除头部元素
LinkedHashMap.Entry first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
//是否删除元素
protected boolean removeEldestEntry(Map.Entry eldest) {
return false;
}
afterNodeAccess
在put方法中还调用了afterNodeAccess(e);都知道HashMap可以重复添加元素(重复key相当于更新value,key始终是唯一的),在HashMap中已经有key1了,这个时候在put(key1,..)那就是更新value值,不管是get,还是put更新都会触发afterNodeAccess(访问后)方法,不管是更新还是访问都是一种访问.
HashMap--put方法中出发:
void afterNodeAccess(Node e) { // 将访问的元素e移动到队列尾部
LinkedHashMap.Entry last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry p =
(LinkedHashMap.Entry)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;
}
}
LinkedHashMap中重写了HashMap的get方法,简单明了
public V get(Object key) {
Node e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder) //是否按访问排序,如果等于true则调整顺序
afterNodeAccess(e);
return e.value;
}
这段代码自己画个图更一下就会明白.
node1 node2 node3 node4
访问了node2或者更新了node2,都会出发afterNodeAccess方法,该方法就是将node2移动值链表尾部.移动后变成
node1 node3 node4 node2
奇怪点解说
void afterNodeAccess(Node e){
LinkedHashMap.Entry p = (LinkedHashMap.Entry)e
}
在put是,newNode的时候LinkedHashMap创建的是一个LinkedHashMap.Entry但是返回的是一个Node数据给HashMap,HashMap拿到的这个Node其实里面隐藏了Entry的前节点后节点.只不过通过Node无法直接访问.
当节点传递过来之后(LinkedHashMap.Entry)e 强转义之后,before,after节点就会自动回来的.
afterNodeRemoval
LinkedHashMap继承了HashMap, LinkedHashMap只在有需要的地方重写了HashMap的方法,其他都是原用HashMap的方法的,同时它并没有改变HashMap的任何结构.只是自己扩展了一个链表.在LinkedHashMap中删除操作就没有任何重写操作,所以HashMap中删除一个元素时,同时也需要在LinkedHashMap中的链表中删除该节点.这个时候就有了扩展方法afterNodeRemoval,只是为了HashMap删除的同时删除在链表中也将该元素删除.
void afterNodeRemoval(Node e) { // unlink
LinkedHashMap.Entry p =
(LinkedHashMap.Entry)e, b = p.before, a = p.after;
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
FIFO算法
FIFO(先进先出)的思想是基于队列。如果一个数据是最先进入的,那么可以认为在将来它被访问的可能性很小。空间满的时候,最先进入的数据会被最早置换(淘汰)掉。
适合热点数据为新数据,最先存入的数据为最老的数据,最后存入的为新数据,当超过队列长度时,删除队列头部数据(老数据)即可.
private static class MyFIFO extends LinkedHashMap {
protected int maxElements;
public MyFIFO(Integer maxSize) {
super(maxSize, 0.75f, false); //FIFO false:访问元素时,不移动。
this.maxElements = maxSize;
}
//当总数大于最大值时,执行删除队列头的动作
protected boolean removeEldestEntry(Map.Entry eldest) {
return this.size() > this.maxElements;
}
}
private static void test(Map map) {
//true表示如果最近访问的,将数据移动到队列尾部。
for (int i = 0; i < 20; i++) {
if (i == 11) {
map.get("k8");//访问后,元素被移动到队列尾部去了。
}
if (i == 18) {
map.get("k14");
}
map.put("k" + i, i);
}
System.out.println("===开始遍历元素====");
for (Map.Entry m : map.entrySet()) {
System.out.println(m.getKey() + "," + m.getValue());
}
}
LRU算法
LRU(The Least Recently Used,最近最久未使用算法)的思想是基于时间,如果一个元素最久没有使用到,那么在将来中使用到的几率也最少,可以将最久未使用的元素从容器中剔除。
可以利用LinkedHashMap的访问顺序存储.被访问过元素移动到队列尾部,长时间未被访问的元素自然在队列头部了,当存入的元素大于队列时,只需要移除队列头的元素即可.
private static class MyLRU extends LinkedHashMap {
protected int maxElements;
public MyLRU(Integer maxSize) {
super(maxSize, 0.75f, true); //区分点true:每次访问元素,都将元素移动到队列尾部
this.maxElements = maxSize;
}
//当总数大于最大值时,执行删除队列头的动作
protected boolean removeEldestEntry(Map.Entry eldest) {
return this.size() > this.maxElements;
}
}
LFU算法
这个如果用redis来实现就会更简单了。
LFU(Least Frequently Used ,最近最少未使用算法)的思想是基于频率,如果一个元素的使用频率最少,那么在将来使用到的情况也最少.这个算法需要记录元素的访问次数,然后淘汰访问次数最少的元素.
LFU和LRU看似相同,但其实不同,不同点在于新加入的元素。
LRU是每次访问都把元素插入到队列尾,新加入的元素也在队列尾巴,超出队列规定长度后淘汰队列头部。
缺点比如:1,2,3,4,5,6,7,8,9,10都是经常被访问的数据,但是队列大小只有10,这个时候插入11也许并不是热点数据,但是规则的问题还是会把1给删掉.
LFU是把每次访问的元素值+1,淘汰count值最小的。根据访问量排序,把访问量少的给淘汰掉。
LFU也有缺点,那就是新加入的元素由于没有访问量,也许就被误杀了.
private static class MyLFU extends HashMap {
public PriorityQueue> queue = new PriorityQueue<>();
protected int maxElements;
public MyLFU(Integer maxSize) {
this.maxElements = maxSize;
}
public V put(K k, V v) {
queue.add(new Node<>(k, 1));//初始值为1
if (size() > maxElements) {
//poll弹出队列头
remove(queue.poll().key);
}
return super.put(k, v);
}
public V get(Object k) {
for (Node kNode : queue) {
if (k != null && k.equals(kNode.key)) {
kNode.setCount(kNode.getCount() + 1);
}
}
return super.get(k);
}
}
//排序后,count值小的排在最前面,当执行poll时,count值最小的也就会被最先弹出。
private static class Node implements Comparable> {
private K key;
private int count;
public Node(K key, int count) {
this.key = key;
this.count = count;
}
public K getKey() {
return key;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public String toString() {
return "[key=" + key + ", count=" + count + "]";
}
@Override
public int compareTo(Node o) {
return this.count - o.count;
}
}
private static void testLFU() {
MyLFU map = new MyLFU<>(10);
for (int i = 0; i < 20; i++) {
if (i == 9) {
map.get("k8");//访问后,元素被移动到队列尾部去了。
}
if (i == 6) {
map.get("k5");
map.get("k3");
}
map.put("k" + i, i);
}
for (int i = 0, n = map.queue.size(); i < n; i++) {
System.out.println(map.queue.poll());
}
}
java lru lfu_Java集合之LinkedHashMap实现LRU,LFU,FIFO算法相关推荐
- java mysql lru_Java集合详解5:深入理解LinkedHashMap和LRU缓存
今天我们来深入探索一下LinkedHashMap的底层原理,并且使用linkedhashmap来实现LRU缓存. 摘要:HashMap和双向链表合二为一即是LinkedHashMap.所谓Linked ...
- Java集合详解5:深入理解LinkedHashMap和LRU缓存
<Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...
- LinkedHashMap实现LRU算法
LinkedHashMap 概述 笔者曾提到,HashMap 是 Java Collection Framework 的重要成员,也是Map族(如下图所示)中我们最为常用的一种.不过遗憾的是,Hash ...
- 缓存基础----LRU算法和FIFO算法的Java实现
Java里面实现LRU缓存算法的通常有两种选择,一种是自己设计数据结构:链表+HashMap(链表用来表示位置,哈希表用来存储和查找),另一种是使用Java中的LinkedHashMap.我们这边文章 ...
- 通过分析LinkedHashMap了解LRU
我们都知道LRU是最近最少使用,根据数据的历史访问记录来进行淘汰数据的.其核心思想是如果数据最近被访问过,那么将来访问的几率也更高.在这里提一下,Redis缓存和MyBatis二级缓存更新策略算法中就 ...
- java中各种集合的用法和比较
一,java中各种集合的关系图 Collection 接口的接口 对象的集合 ├ List 子接口 按进入先后有序保存 可重复 │├ L ...
- Java中Set集合是如何实现添加元素保证不重复的?
点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | 公众号「武培轩」 Java中Set集合是如何实 ...
- 【Java 集合】Java 集合的线程安全性 ( 加锁同步 | java.utils 集合 | 集合属性 | java.util.concurrent 集合 | CopyOnWrite 机制 )
文章目录 I . Java 集合的线程安全概念 ( 加锁同步 ) II . 线程不安全集合 ( 没有并发需求 推荐使用 ) III . 集合属性说明 IV . 早期的线程安全集合 ( 不推荐使用 ) ...
- Thinking in java基础之集合框架
Thinking in java基础之集合框架 大家都知道我的习惯,先上图说话. 集合简介(容器) 把具有相同性质的一类东西,汇聚成一个整体,就可以称为集合,例如这里有20个苹果,我们把每一个苹果当成 ...
最新文章
- 值得期待的.Net Micro Framework 3.0
- java pom.xml 自定义变量
- argparse模块用法
- shell基础之变量及表达式
- 怎么扒站建站_个人怎么做独立站Shopify商店!Shopify建站教程详解!(实操干货)...
- js 图片浏览插件原生
- Advanced.MP3.WMA.Recorder.v5.8.WinAll-CAT
- CAD导出JPG如何控制转换质量?
- jeecgboot修改登录界面、背景图等的页面记录
- 对称网络的电路分析方法
- 安卓socket客户端
- iOS微信发布8.0.29版本,苹果14用户快来
- (全)Word Embedding
- Linux命令行修改配置(待续)
- 剑指offer答案 c语言,剑指offer之C语言不修改数组找出重复的数字
- c语言编程解释,c语言编程,请高手一字一句解释
- ps cc2019版为什么做图一复制图层就卡死_PS制作一张具有故障艺术效果的人物海报...
- Access to XMLHttpRequest at ‘http://xx‘ from origin ‘http://xx‘ has been blocked by CORS policy:
- 公司企业邮箱,邮件归档和邮件备份有什么用途?
- 图片懒加载——lazyload