黄金挑战:LRU页面置换算法
LRU算法是操作系统中常用的页面置换算法
对应leetcode146. LRU 缓存
本题LRU算法是通过HashMap+双向链表数据结构来实现的;
hashMap用于存放key,和链表中对应的节点node;
双向链表实现置换的功能;当链表中存在的节点被访问时,该节点会移动到头节点位置;如果在添加节点时出现,链表中节点大小>=capacity时;就会出现删除链表尾部节点,同时将新节点插入原头节点位置;
图解:
实现代码:
/*
* 最近最少使用算法,使用hash+双向链表来实现;
* */
import com.demo.linkedlist.ListNode;
import java.util.HashMap;
import java.util.Map;
public class LRUCache {
// 自定义双向链表结构;
static class DLinkedNode {
int key;
int value;
DLinkedNode next; // 指向后一个节点
DLinkedNode prev; // 指向前一个节点
public DLinkedNode() {
}
public DLinkedNode(int key, int value) {
this.key = key;
this.value = value;
}
}
// cache用来存放 key,以及链表中的节点
private Map<Integer, DLinkedNode> cache = new HashMap<>();
private Integer size; // 当前链表中节点的个数
private Integer capacity; // 链表允许的最大节点个数
private DLinkedNode head, tail; // 双向链表的虚拟头节点和尾节点;
public LRUCache(int capacity) {
this.capacity = capacity;
this.size = 0;
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
// 用来获取缓存机制中对应key的值;
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
} else {
moveToHead(node);
return node.value;
}
}
// 用于将新的key,value加入到缓存机制中;
public void put(int key, int value) {
// 先从map中判断当前node是否已经存放
DLinkedNode node = cache.get(key);
// 未存放
if (node == null) {
// 创建一个新的节点
DLinkedNode curNode = new DLinkedNode(key, value);
// 存入cache中
cache.put(key, curNode);
// 如果size == capacity 就说明需要移除掉链表尾部节点
if (size >= capacity) {
DLinkedNode tail = removeTail();
// 将cache中的存放的尾节点删除;
cache.remove(tail.key);
// size--;
size--;
}
// 将新节点插入链表头部
addToHead(curNode);
// 当前链表的节点个数+1;
size++;
} else {
//说明已经链表中已经存放该节点,只需将链表中的节点移动到头部,同时替换原有的节点的值;
node.value = value;
moveToHead(node);
}
}
// 头插法
private void addToHead(DLinkedNode curNode) {
// 让当前节点的prev指针指向head;next指针指向原来head指向的节点,即head.next;
curNode.prev = head;
curNode.next = head.next;
// 原来head指向的节点的prev指针指向当前插入节点
head.next.prev = curNode;
// 让head的next指向当前插入节点
head.next = curNode;
}
// 删除链表中的指定节点
private void removeMidNode(DLinkedNode curNode) {
curNode.next.prev = curNode.prev;
curNode.prev.next = curNode.next;
}
// 将指定的节点移动到头结点的位置
private void moveToHead(DLinkedNode curNode) {
removeMidNode(curNode);
addToHead(curNode);
}
// 移除尾部节点,需要进行返回的目的是为了删除掉cache中原有的尾节点
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeMidNode(res);
return res;
}
// 测试LRU结构
public static void main(String[] args) {
// 初始化一个容量为3的LRUCache结构
LRUCache cache=new LRUCache(3);
// 放入3个元素,此时链表中的节点应该是 (3,3) -> (2,2) -> (1,1);
cache.put(1,1);
cache.put(2,2);
cache.put(3,3);
// get(1)命中缓存,将节点(1,1)移动到头节点位置,链表变成 (1,1) -> (3,3) -> (2,2);
int res = cache.get(1);
// put(1,10)之后,链表节点变为 (1,10) -> (3,3) -> (2,2);
cache.put(1,10);
// get(2)之后,链表节点变为 (2,2) -> (1,10) -> (3,3);
res=cache.get(2);
}
}
最终测试结果如下图,链表结构为
map中存放的元素为