LRU全称是Least Recently Used 最近最少使用.
设计原则:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。那么当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。
常被用于内存淘汰机制,redis,memcached,ehcache,mongodb等等都有采用此算法.意思就是存放数据的缓存满了,现在要根据一些条件删除掉一些数据,条件就是最近一段时间周期内的数据使用次数最少的一些会被筛选出来然后删掉.
先来一个简单实现,感受一下
package com.cn.demo;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 继承的LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构。
* 该结构由数组和链表+红黑树,在此基础上LinkedHashMap 增加了一条双向链表,
* 保持遍历顺序和插入顺序一致的问题
* @param <k>
* @param <v>
*/
public class LRUCache<k,v> extends LinkedHashMap<k,v> {
private final int cache_size;//缓存大小
/**
* @param initialSize 初始数组长度
* @param accessOrder false:基于插入顺序(默认) true:基于访问顺序
*/
public LRUCache(int initialSize,boolean accessOrder){
//loadFactor装载因子默认值0.75F,表示数组的数组长度超过这个比例,数组就要扩容,
//这个比例越大,存放的数据越多,空间利用率越高,但冲突的机会加大了.
//反之,比例越小,存放的元素越少,冲突的机会减小,但空间浪费多了.
//冲突的机会越大,查找的成本越高.反之,查找的成本越小.
//因此,必须在 "出现冲突的机会"和"空间利用率"之间寻找平衡所以使用了折中的想法。
//但是指定了初始数组长度这个装载因子就没作用了哦
super(initialSize,0.75F,accessOrder);
this.cache_size=initialSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<k, v> eldest) {
//意思就是当这个传递进来的map中的数据量大于指定的缓存数据的数量,就自动删除最老的数据.
if(size()>cache_size){
return true;
}
return false;
}
public static void main(String[] args) {
//accessOrder true表示按照访问顺序进行排序,
//使用LinkedHashMap的get和put方法会执行它的afterNodeAccess方法,将节点移动到最后
LRUCache lruCache=new LRUCache<String,Object>(6,true);
lruCache.put("1","一");
lruCache.put("2","二");
lruCache.put("3","三");
lruCache.put("4","四");
lruCache.put("5","五");
lruCache.put("6","六");
for (Object item:lruCache.keySet()) {
System.out.printf(item.toString());//123456
}
lruCache.get("1");
System.out.println("");
for (Object item:lruCache.keySet()) {
System.out.print(item.toString());//234561
}
System.out.println("");
lruCache.put("7","七");
for (Object item:lruCache.keySet()) {
System.out.print(item.toString());//345617
}
//观察上述打印的注释,代入场景,初始化缓存的时候给的内存大小是6,可以存放6条数据,
//当一条数据被访问过后,它就不是最老和使用次数最少的数据了,所以被移动到了底部.
//当新插入一条数据的时候,也会被放入底部,因为是新数据.
//但这个时候要注意,第三次打印的时候元素2不在了,原因是内存只容许放6条数据,
//超出的部分要被删除掉,那么就只能删除早早被写入但又没人使用的数据,
}
}
这边debug带大家看一下 lruCache.put("7","七"); 这一行put添加的代码是怎么执行完后就把"最少使用的"数据删除掉了
HashMap类里的put方法,在结尾有个方法叫 afterNodeInsertion ,意思就是插入新节点但是可能删除最大节点,
只要符合条件就会删除,前面咱们重写的removeEldestEntry里面的逻辑就是用来判断是否需要进行删除
进到方法里判断时候会发现当前map的size大于指定的数量,所以返回true,最后会执行removeNode方法把first里的节点元素删除掉
ps:想了解LinkedHashMap更底层的实现,请看隔壁文章.