LRU算法
1. 什么是LRU算法
LRU是Least Recently Used的缩写,即最近最久未使用,常用于页面置换算法,是为虚拟页式存储管理服务的。
LRU算法的提出,是基于这样一个事实:在前面几条指令中使用频繁的页面很可能在后面的几条指令中频繁使用。反过来说,已经很久没有使用的页面很可能在未来较长的一段时间内不会被用到。
设计并实现了一个最近最少使用(LRU)缓存的数据结构,它应该支持以下操作:get和put。
get(key)——如果键存在于缓存中,则获得键值(总是正数),否则返回-1。
put(key value)——如果键不存在,则设置或插入值。当缓存达到其容量时,应在插入新项之前使最近最少使用的项无效。
LRU缓存可使用HashMap和双向链表实现。HashMap使得get的时间是O(1);双向链表使节点添加/删除操作O(1)。
2. Java实现LRU算法
数据结构:
/**
* 定义一个双向链表结构
*/
public class Node {
int key;
int value;
// 上一个节点
Node pre;
// 下一个节点
Node next;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
import java.util.HashMap;
/**
* 缓存类
*/
public class LRUCache {
int capacity;
HashMap<Integer, Node> map = new HashMap<Integer, Node>();
Node head = null;
Node tail = null;
public LRUCache(int capacity) {
// 容器容量
this.capacity = capacity;
}
public int get(int key) {
// 判断key是否已经存在
if (map.containsKey(key)) {
// 存在取出节点
Node n = map.get(key);
// 删除节点在双向链表中的位置
remove(n);
// 将当前节点设置为头节点
setHead(n);
return n.value;
}
return -1;
}
/**
* 删除节点在双向链表中的位置
* @param n
*/
public void remove(Node n) {
if (n.pre != null) {
// 将n的上一个节点的下一个节点指向n的下一个节点
n.pre.next = n.next;
} else {
// 如果n的上一个节点为null,说明n是头节点,将n的下一个节点设置为头节点
head = n.next;
}
if (n.next != null) {
// 将n的下一个节点的上一个节点指向n的上一个节点
n.next.pre = n.pre;
} else {
// 如果n的下一个节点为null,说明n是尾节点,将n的上一个节点设置为尾节点
tail = n.pre;
}
}
/**
* 将n设置为头节点
* @param n
*/
public void setHead(Node n) {
// 将n的下一个节点指向现在的头节点
n.next = head;
// 将n的上一个节点指向null,因为n要为头节点
n.pre = null;
// 如果之前头节点不为空,则将之前头结点的上一个节点指向n
if (head != null)
head.pre = n;
// 将头节点设置为n
head = n;
// 如果尾节点为空,说明只有一个元素,把尾节点设置为和头节点一样的元素
if (tail == null)
tail = head;
}
/**
* 取出元素
* @param key
* @param value
*/
public void put(int key, int value) {
// 判断key是否已经存在
if (map.containsKey(key)) {
// 存在,取出老节点
Node old = map.get(key);
// 将老节点值设置为最新值
old.value = value;
// 删除老节点在双向链接中的位置
remove(old);
// 将老节点设置为头节点
setHead(old);
} else {
// 创建一个节点
Node created = new Node(key, value);
// 判断当前map容器是否超过最大值
if (map.size() >= capacity) {
// 超过最大值则删除(通过双向链表中尾节点记录的key进行删除)
map.remove(tail.key);
// 删除双向链表尾节点元素
remove(tail);
// 把当前节点设置为头节点
setHead(created);
} else {
// 把当前节点设置为头节点
setHead(created);
}
// 存入map中
map.put(key, created);
}
}
}