LRU算法
LRU(Least Recently Used),即最近最少使用算法。常用于实现一个简单的缓存功能,就是把很久未使用的直接移除掉,只保留最近使用的。
LRU主要需要实现两个功能
添加缓存(涉及到删除缓存)
获取缓存
实现原理
一个单链表就能实现简单的LRU算法:但是链表的查找时间复杂度比较高了,是O(n)。
一个散列表+双链表实现一个O(1)复杂度的LRU算法:用散列表就可以直接定位某个缓存,时间复杂度O(1),但是散列表插入缓存之后,就没有了顺序,所以才需要一个链表来维护这个缓存的顺序,超过缓存最大容量之后需要删除未使用的缓存。而如果单链表删除某个缓存的话,又需要先遍历这个元素(时间复杂度O(n))才行。所以这里用双链表就可以在O(1)时间复杂度内删除这个缓存了。
package com.arithmetic.code;
import java.util.HashMap;
import java.util.Map;
public class LRUCache {
private int cacheSize = 10; // map 长度
private Map<String,Node> map = new HashMap<>();
private Node head; // 头部节点
private Node tail; // 尾部节点
public void LRUCache(int cacheSize) {
this.cacheSize = cacheSize;
}
class Node{
String key;
String value;
Node pre; // 当前节点的上级引用
Node next; // 当前节点的下级引用
public Node(String key,String value){
this.key = key;
this.value = value;
}
}
/**
* 向缓存中添加值,head为最不常用值,tail为最新值
* 链表头部为最不常用值,最新访问或者添加的元素置于链表尾部
*/
public void addCache(String key,String value){
if(map.containsKey(key)){
// 判断Node所在位置
Node node = map.get(key);
if(node.next != null){ // 说明node肯定不是在链表结尾
if(node.pre == null){ // node位于头部
head = node.next;
// 注意:新的head节点需要将pre置为null,不可省略,否则新的头部节点会存在之前的上级引用
head.pre = null;
} else { // node位于链表中间
node.pre.next = node.next;
node.next.pre = node.pre;
}
//此时将node 放在结尾
tail.next = node;
node.pre = tail;
node.next = null;
tail = node;
}
} else { // map中不存在该元素
Node node = new Node(key,value);
if(map.size() == cacheSize){ // map已到最大存储范围
// 将头部节点删除,然后再尾部添加最新元素
Node temp = head;
head = head.next;
// 新的head节点需要将pre置为null
head.pre = null;
map.remove(temp.key);
//此时将node 放在尾部
tail.next = node;
node.pre = tail;
node.next = null;
tail = node;
} else {
if(map.size() == 0){
head = node;
tail = node;
} else {
tail.next = node;
node.pre = tail;
// 新的head节点需要将pre置为null
node.next = null;
tail = node;
}
}
map.put(key,node);
}
}
// 从当前缓存中获取值
public String getCache(String key){
if(map.containsKey(key)){ // 现有元素包含key
Node node = map.get(key);
if(node.next != null){
if(node.pre == null){ // 位于头部
head = node.next;
// 新的head节点需要将pre置为null
head.pre = null;
} else {
node.pre.next = node.next;
node.next.pre = node.pre;
}
node.pre = tail;
tail.next = node;
node.next = null;
tail = node;
}
return node.value;
} else {
return null;
}
}
// 进行测试
public static void main(String[] args) {
LRUCache cache = new LRUCache();
cache.addCache("key0", "value0");
cache.addCache("key1", "value1");
cache.addCache("key2", "value2");
cache.addCache("key3", "value3");
cache.addCache("key4", "value4");
cache.addCache("key5", "value5");
cache.addCache("key6", "value6");
cache.addCache("key7", "value7");
cache.addCache("key8", "value8");
cache.addCache("key9", "value9");
//从此处开始map已满,新添加的元素会将头部删除,自身放于链表末尾
cache.addCache("key10", "value10");
cache.addCache("key11", "value11");
cache.addCache("key12", "value12");
// debug 程序可知添加此node后 head 为key4,tail 为key13
cache.addCache("key13", "value13");
// debug 程序可知map中head 为key5,tail 为key4(即将自身从头部移动到尾部)
cache.getCache("key4");
}
}