什么是LRU
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。(来源于百度百科)
其实LRU这个概念映射到现实生活中非常好理解,就好比说小陈的衣柜中有很多衣服,假设他的衣服都只能放在这个柜子里,小陈每过一阵子,小陈就会买新衣服,不久小陈的衣柜就放满了衣服。这个小陈想了个办法,按照衣服上次穿的时间排序,丢掉最长时间没有穿过那个。这就是LRU策略。
如何实现
我们可以使用java里面的LinkedHashMap或者HashMap来实现。
用LinkedHashMap实现
这是最简单的方式去实现LRU,在这里我不对LinkedHashMap的源码做过多的讲解。
在实现LRU策略之前,我们需要知道创建一个类,然后继承LinkedHashMap,然后复写removeEldestEntry方法,在这里我会说一说,为什么要这样做。
首先我们看看LinkedHashMap相对于HashMap新增了哪些属性。
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{
//链表头
transient LinkedHashMap.Entry<K,V> head;
//链表尾
transient LinkedHashMap.Entry<K,V> tail;
//继承Node,为数组的每个元素增加了before和after属性
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
//控制两种访问模式的字段,默认false
//true按照访问顺序,会把经常访问的key放到队尾
//false按照插入顺序提供访问
final boolean accessOrder;
//。。。。
}
所以在实现中我们需要将accessOrder = true
然后我们说说为什么要复写removeEldestEntry方法。
要点
- LinkedHashMap中是继承了HashMap来实现的。
- LinkedHashMap的put其实是由HashMap来实现的。
- LinkedHashMap覆写了put方法执行中调用的newNode/newTreeNode,afterNodeAccess和afterNodeInsertion
知道这些以后,我们在来看看put()方法,put()方法会调用putVal()方法。
发现在最后会调用afterNodeInsertion方法,在这里调用的是LinkedHashMap中方法。
接着查看removeEldestEntry方法
发现默认情况下返回false,所以不复写前不会删除第一个节点,所以我们要复写这个方法。
最终源码
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class LRULinkedHashMap extends LinkedHashMap {
private int initialCapacity;
private static float loadFactor = 0.75f;
private static boolean accessOrder = true;
//缓存池的最大容量
private int maxCapacity ;
public LRULinkedHashMap(int initialCapacity,int maxCapacity) {
super(initialCapacity, loadFactor, accessOrder);
this.maxCapacity = maxCapacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return super.size() > maxCapacity;//超过容量的时候直接删除
}
public static void main(String[] args) {
LRULinkedHashMap map = new LRULinkedHashMap(4,3);
map.put(1,1);
map.put(2,2);
map.put(3,3);
map.put(4,4);
map.put(2,6);
Set set = map.entrySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
Map.Entry next = (Map.Entry) iterator.next();
Integer key = (Integer) next.getKey();
Integer value = (Integer) next.getValue();
System.out.println(key+":::"+value);
}
}
}
用HashMap实现
这种实现方式我直接展示源码,里面我已经将注释写的很明白了。
import java.util.HashMap;
//用于构造双向链表
class CacheNode<K,V>{
CacheNode pre;
CacheNode next;
K key;
V value;
@Override
public String toString() {
return key+" "+value;
}
}
public class LRUCache<K,V> {
//用HashMap来存储元素
private HashMap<K,CacheNode<K,V>> caches;
//头部
private CacheNode head;
//尾部
private CacheNode tail;
//缓存池的最大容量
private int maxCapacity;
public LRUCache(int capacity, int maxCapacity){
caches = new HashMap<>(capacity);//初始化HashMap
this.maxCapacity = maxCapacity;
}
/**
* 添加元素
*/
public void put(K key,V value){
//判断元素是否已存在缓存池
CacheNode<K, V> node = this.caches.get(key);
//此节点已经存在,将其移动到尾部,并修改其value的值
if(node != null){
//此元素存在
node.value = value;
moveToTail(node);
}else{
//判断缓存池是否已满
if(caches.size() >= this.maxCapacity){
//已满,删除第一个元素
removeFirst();
}
//此节点不存在,添加元素
node = new CacheNode<>();
node.key = key;
node.value = value;
//将元素保存到HashMap当中。
caches.put(node.key,node);
//添加元素我们都是往尾部添加元素
//这里有两种情况:1,双向链表没有初始化。2,双向链表初始化成功。
if(head == null){
//初始化双向链表
head = node;
tail = node;
tail.pre = node;
}else {
//双向链表初始化成功,缓存中至少存在一个元素
tail.next = node;
node.pre = tail;
node.next = null;
tail = node;
}
}
}
/**
* 得到指定元素
*/
public CacheNode get(K key){
CacheNode<K, V> node = caches.get(key);
if(node == null){
return null;
}
//此节点移动到尾部
moveToTail(node);
return node;
}
/**
* 删除指定元素的节点
*/
public void remove(K key){
CacheNode<K, V> node = caches.remove(key);
if(node == null){
//节点不存在
return ;
}
if(head == tail){
//删除缓存池中唯一的一个元素
caches.remove(key);
head = tail = null;
return ;
}
else if(head == node){
//删除缓存池中的第一个元素
head = head.next;
head.pre = null;
return ;
}
else if(tail == node){
//删除尾部元素
tail = tail.pre;
tail.next = null;
return ;
}
//删除中间元素
CacheNode preNode = node.pre;
CacheNode nextNode = node.next;
preNode.next = nextNode;
nextNode.pre = preNode;
}
/**
* 得到元素遍历后的结果
*/
@Override
public String toString(){
StringBuilder result = new StringBuilder();
CacheNode tempHead = head;
while(tempHead != null){
result.append(" {"+tempHead.key+":"+tempHead.value+"} ");
tempHead = tempHead.next;
}
return result.toString();
}
/**
* 节点存在将其移动到尾部
*/
private void moveToTail(CacheNode<K,V> node) {
if(head == tail){
//缓存中只存在一个元素,而且命中,不需要移动。
return;
}
else if(tail == node){
//当这个节点是尾部元素时,不需要移动。
return;
}
if(head == node){
//当这个节点是首元素时
node.pre = null;
head = node.next;
}
//其他情况
CacheNode preNode = node.pre;//此节点的前一项
CacheNode nextNode = node.next;//此节点的后一项
preNode.next = nextNode;
nextNode.pre = preNode;
//将node移动到尾部
tail.next = node;
node.pre = tail;
node.next = null;
tail = node;
}
/**
* 缓存池已满,删除第一个元素
*/
private void removeFirst() {
if(head == null){
return ;
}
//删除第一个元素
caches.remove(head.key);
//然后双向链表移动到下一个节点
head = head.next;
if(head != null) {
head.pre = null;
}
}
public static void main(String[] args) {
LRUCache<Integer,String> lru = new LRUCache<Integer, String>(4,5);
lru.put(1,"cxf1");
lru.put(2,"cxf2");
lru.put(3,"cxf3");
lru.put(4,"cxf4");
lru.put(5,"cxf5");
lru.put(4,"cxf-cxf-4");
System.out.println(lru.toString());
lru.put(6,"cxf6");
System.out.println(lru.toString());
lru.remove(4);
System.out.println(lru.toString());
}
}