一、LRU算法简介
LRU(Least Recently Used)最近最久未使用算法
常见应用场景:内存管理中的页面置换算法、缓存淘汰中的淘汰策略等
二、实现理论
底层结构:双向链表 + HashMap ,双向链表由特定的哈希节点组成。
(1)访问节点时,将其从原来位置删除,插入到双向链表头部;
(2)更新节点时,先删除原有缓存数据(即原有节点),然后更新map映射,再将更新值作为节点插入链表头;更新后,判断容量是否超过最大内存使用量
(3)超过则执行淘汰;淘汰即删除双向链表最后一个节点,同时删除map中的映射
(4)LRU实现中有频繁的查找节点并删除,为节省时间(链表查找目标节点需要遍历),使用HashMap保存键-节点映射关系,O(1)的查找+O(1)的删除
(5)LRU实现中,要频繁的在头部插入,以及在尾部删除;因此,需要定义head、tail两个节点,方便操作
三、代码
package cache;
import java.util.HashMap;
/**
* 底层结构:双向链表 + HashMap ,双向链表由特定的哈希节点组成。
* <p>
* (1)访问节点时,将其从原来位置删除,插入到双向链表头部;
* (2)更新节点时,先删除原有缓存数据(即原有节点),然后更新map映射,再将更新值作为节点插入链表头;更新后,判断容量是否超过最大内存使用量
* (3)超过则执行淘汰;淘汰即删除双向链表最后一个节点,同时删除map中的映射
* (4)LRU实现中有频繁的查找节点并删除,为节省时间(链表查找目标节点需要遍历),使用HashMap保存键-节点映射关系,O(1)的查找+O(1)的删除
* (5)LRU实现中,要频繁的在头部插入,以及在尾部删除;因此,需要定义head、tail两个节点,方便操作
*/
public class LRUCache<K, V> {
private class Node {
K key;
V val;
Node pre;
Node next;
public Node(K k, V v) {
this.key = k;
this.val = v;
}
}
private Node head;
private Node tail;
private HashMap<K, Node> map;
private int maxSize;
public LRUCache(int maxSize) {
this.maxSize = maxSize;
this.map = new HashMap<>((int) (maxSize / 0.75));
this.head.next = tail;
this.tail.pre = head;
}
//获取指定数据
public V get(K k) {
//判断是否存在对应数据
if (!map.containsKey(k)) {
return null;
}
//先从链表原来位置中删除,然后添加到添加到链表头部
Node node = map.get(k);
removeNode(node);
addFirst(node);
return node.val;
}
public void put(K k, V v) {
if (map.containsKey(k)) {
Node node = map.get(k);
//先删除原有节点
removeNode(node);
}
Node now = new Node(k, v);
//更新内存映射
map.put(k, now);
//插入链表头
addFirst(now);
//判断是否需要淘汰数据
if (map.size() > maxSize) {
removeLast();
//同时删除map中的映射
map.remove(k);
}
}
public void removeNode(Node node) {
Node pre = node.pre;
Node next = node.next;
pre.next = next;
next.pre = pre;
node.pre = null;
node.next = null;
}
public void addFirst(Node node) {
Node next = head.next;
head.next = node;
node.pre = head;
node.next = next;
next.pre = node;
}
public Node removeLast() {
Node last = tail.pre;
removeNode(last);
return last;
}
}