今日心情美丽,让咱们看看传说中的LRU算法是怎么一回事
1.1 何谓LRU算法
LRU算法指的是Least Recently Used,即最近最少使用算法,也被称作缓存淘汰算法/页面置换算法,LRU算法的基本原则是如果一个数据最近很少被访问到,那么之后也应该很少被访问到;如果一个数据最近经常被访问到,那么置换也应该经常被访问到.
1.2 如何实现
实现LRU算法一般有以下几种做法:
- 双向链表实现
通过双向链表链表可以实现一个LRU缓存结果,做法是
1.新添加的数据放置在链头
2.将被访问到的数据移动到链头
3.当链表满载时,将链尾数据丢弃
如图,通过这三步就可以将经常被访问到的数据放置在链头,从而在下次访问时减少从头至尾的对比时间;而不经常被访问的数据则放置在链尾,当链表满载时,将其删除:
![[外链图片转存失败(img-zmQ9Eoz8-1567614666654)(picture/LRU.png)]](https://i-blog.csdnimg.cn/blog_migrate/a4d006f7cd83ed6f9859d9d3f3b13b82.png)
代码实现
/**
* @Auther: ARong
* @Date: 19-9-5 上午00:26
* @Description: 基于双向链表和HashMap的LRU算法实现
**/
public class LRUCache<K, V> {
//链头
private Node head;
//链尾
private Node end;
//链表所容纳的数据量16
private int capacity;
//存储Node的HashMap
HashMap<K, Node> map = new HashMap<>();
//构造方法中指定容量
public LRUCache(int capacity) {
this.capacity = capacity;
}
public LRUCache() {
this.capacity = 16;
}
//双向链表节点
class Node {
Node pre;
Node next;
K key;
V value;
Node(K key, V value) {
this.key = key;
this.value = value;
}
}
/**
* @auther: Arong
* @description: 添加缓存数据
* @param: key value
* @return: void
* @date: 19-9-5 上午00:26
*/
public void add(K key, V value) {
//检查是否需要删除尾部元素
check();
//分情况在链头添加数据
if (map.containsKey(key)) {
//map中含有该缓存,更新其value,并将其置于链表首部
Node node = map.get(key);
node.value = value;
removeNode(node);
putHead(node);
} else {
//map中不含有该数据,直接添加到map中和链表头部即可
Node node = new Node(key, value);
map.put(key, node);
putHead(node);
}
}
/**
* @auther: Arong
* @description: 将Node置于链表头部
* @param: [node]
* @return: void
* @date: 19-9-5 上午00:26
*/
private void putHead(Node node) {
if (node == end) {
end = node.pre;
}
if (end == null) {
end = node;
}
node.pre = null;
node.next = head;
if (head != null) {
head.pre = node;
}
head = node;
}
/**
* @auther: Arong
* @description: 检查是否需要删除尾部数据
* @param: []
* @return: void
* @date: 19-9-5 上午00:26
*/
private void check() {
if (map.size() == capacity) {
//需要删除尾部元素
removeNode(end);
}
}
/**
* @auther: Arong
* @description: 获取缓存数据
* @param: [key]
* @return: V
* @date: 19-9-5 上午00:26
*/
public V get(K key) {
//在链表中搜索该数据
if (map.containsKey(key)) {
//将其V返回.并且将Node置于链头
Node node = map.get(key);
removeNode(node);
putHead(node);
return node.value;
} else {
return null;
}
}
/**
* @auther: Arong
* =@description: 删除缓存
* @param: [key]
* @return: V
* @date: 19-9-5 上午00:26
*/
public V remove(K key) {
if (map.containsKey(key)) {
Node node = map.get(key);
map.remove(key);
removeNode(node);
return node.value;
} else {
return null;
}
}
/**
* @auther: Arong
* @description: 删除节点
* @param: [node]
* @return: void
* @date: 19-9-5 上午00:26
*/
private void removeNode(Node node) {
if (node.pre != null) {
node.pre.next = node.next;
} else {
head = node.next;
head.pre = null;
}
if (node.next != null) {
node.next.pre = node.pre;
} else {
end = node.pre;
}
}
}
1.3 为何不直接使用HashMap作为LRU底层结构
1.若不限定HashMap的容量,那么至多可以存储2^30个缓存数据,但是多于这个就无法再存入数据,并且缓存数据过多,内存占用巨大会产生OOM问题; 2.若限定HashMap的容量,例如限定为16,使用put时还可以直接放入HashMap中,但若数量超过了16,由于无法记录哪个数据被访问到得最少,那么就无法删去数据,也就无法做到动态地维持容量平衡;1.4 基于LinkedHashMap实现LRUCache
下面介绍一种实现LRU的更简单的方式,它基于java.util.LinkedHashMap,LinkedHashMap底层采用的就是上文中链表+HashMap的实现,原理和上文一致:
public class LRUCacheByLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
private int capacity;
public LRUCacheByLinkedHashMap(int capacity) {
super(capacity, 0.75f, true);
this.capacity = capacity;
}
/**
* @auther: Arong
* @description: 重写删除最后访问元素的方法
* @param: [eldest]
* @return: boolean
* @date: 下午6:52 19-9-5
*/
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
//LinkedList在每次get时会调用该方法去判断需不需要删除最久没有被访问到的元素,返回true则删除
return super.size() > capacity;
}
/**
* @auther: Arong
* @description: 添加元素
* @param: [key, value]
* @return: void
* @date: 下午6:49 19-9-5
*/
public void add(K key, V value) {
super.put(key, value);
}

本文深入解析了LRU(最近最少使用)算法,一种常见的缓存淘汰策略。文章详细介绍了LRU算法的定义、实现方式,包括使用双向链表和HashMap进行实现的过程,并解释了为何不直接使用HashMap作为LRU的底层结构。此外,还提供了一种基于LinkedHashMap的简化实现方案。
394

被折叠的 条评论
为什么被折叠?



