LRU(Least Recently Used)最近最少使用,是一种页面置换算法,自认为这个翻译有点不太贴切。LRU通俗来说就是在一块大小有限的内存中,最近访问的(包括增改查)数据放到最前面,当加入新的数据时,如果已满,则需要先删除最后面的数据。所以就是将最近使用的放到最前面。
hashmap+双向链表实现
import java.util.HashMap;
import java.util.Map;
public class LRUCache {
// 定义一个内部节点类
class LNode{
int key; // 用以删除hashmap中对应的节点
int value; // 数据
LNode pre;
LNode next;
}
/*
* 在头节点后面增加新节点
*/
private void addNode(LNode node) {
head.next.pre = node;
node.next = head.next;
head.next = node;
node.pre = head;
}
/*
* 删除一个节点
*/
private void removeNode(LNode node) {
LNode node1 = node.pre;
LNode node2 = node.next;
node1.next = node2;
node2.pre = node1;
}
/*
* 将节点移动到头部
*/
private void moveToHead(LNode node) {
removeNode(node);
addNode(node);
}
private LNode head; // 头节点
private LNode tail; // 尾节点
private int size; // 占用量
private int capacity; // 容量
private Map<Integer, LNode> cache = new HashMap<Integer, LRUCache.LNode>();
/*
* 构造函数
*/
public LRUCache(int capacity) {
this.capacity = capacity;
this.size = 0;
head = new LNode();
tail = new LNode();
head.next = tail;
tail.pre = head;
}
/*
* 获取内存占用量
*/
public int getSize() {
return this.size;
}
/*
* 获取数据
*/
public int get(int key) {
LNode node = cache.get(key);
if(node == null) { // 空
return Integer.MIN_VALUE;
}
moveToHead(node); // 将访问的节点移动到最前面
return node.value;
}
/*
* 修改数据,如不存在,则添加数据
*/
public void put(int key, int value) {
LNode node = cache.get(key);
if(node == null) { // 增加
LNode newNode = new LNode();
newNode.key = key;
newNode.value = value;
if(size == capacity) { // 容量已用完
// 删除队尾节点
cache.remove(tail.pre.key);
removeNode(tail.pre);
size--;
}
// 添加新数据
addNode(newNode);
cache.put(key, newNode);
size++;
}
else { // 修改
node.value = value;
moveToHead(node);
}
}
/*
* 删除数据
*/
public void remove(int key) {
LNode node = cache.get(key);
if(node == null) {
return;
}
removeNode(node);
cache.remove(key);
}
/*
* 重写toString,为了测试
*/
public String toString() {
StringBuilder strB = new StringBuilder();
strB.append("{");
LNode node = head.next;
while(node != tail) {
strB.append(node.key +":"+ node.value + ", ");
node = node.next;
}
if(strB.length() > 1) { // 将最后一个逗号及空格删除
strB.delete(strB.length()-2, strB.length()-1);
}
strB.append("}");
return strB.toString();
}
public static void main(String[] args) { // 测试
LRUCache lru = new LRUCache(3);
lru.put(1, 5); // 增加
lru.put(2, 2);
lru.put(3, 6);
System.out.println("增加:" + lru.toString());
lru.put(1, 3); // 修改
System.out.println("修改:" + lru.toString());
lru.put(4, 0); // 过量增加
System.out.println("过量增加:" + lru.toString());
System.out.println("获取:" + lru.get(1));
System.out.println("获取:" + lru.toString());
lru.remove(4); // 删除
System.out.println("删除:" + lru.toString());
}
}
运行结果
增加:{3:6, 2:2, 1:5 }
修改:{1:3, 3:6, 2:2 }
过量增加:{4:0, 1:3, 3:6 }
获取:3
获取:{1:3, 4:0, 3:6 }
删除:{1:3, 3:6 }
看到hashmap+双向链表?这不就是LinkedHashMap吗?直接上一个简单粗暴的。
LinkedHashMap实现
这种实现方法和第一种有所不同,其将最近访问的放在最后一个节点,而不是第一个节点。
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCache extends LinkedHashMap<Integer, Integer>{
private int capacity;
/*
* 构造函数
*/
public LRUCache(int capacity) {
super(capacity, 0.75F, true);
this.capacity = capacity;
}
/*
* 获取数据
*/
public int get(int key) {
return super.getOrDefault(key, -1);
}
/*
* 修改或增加数据
*/
public void put(int key, int value) {
super.put(key, value);
}
/*
* 删除数据
*/
public void remove(int key) {
super.remove(key);
}
/*
* 返回true表示删除最后一个节点
* 其调用在putVal()方法中的afterNodeInsertion()方法
*/
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
public static void main(String[] args) { // 测试
LRUCache lru = new LRUCache(3);
lru.put(1, 5); // 增加
lru.put(2, 2);
lru.put(3, 6);
System.out.println("增加:" + lru.toString());
lru.put(1, 3); // 修改
System.out.println("修改:" + lru.toString());
lru.put(4, 0); // 过量增加
System.out.println("过量增加:" + lru.toString());
System.out.println("获取:" + lru.get(1));
System.out.println("获取:" + lru.toString());
lru.remove(4); // 删除
System.out.println("删除:" + lru.toString());
}
}
运行结果
增加:{1=5, 2=2, 3=6}
修改:{2=2, 3=6, 1=3}
过量增加:{3=6, 1=3, 4=0}
获取:3
获取:{3=6, 4=0, 1=3}
删除:{3=6, 1=3}