序言
今天我们一起来剖析一下LRU算法,在Redis中当内存满了后,有一种内存淘汰策略就是LRU即淘汰掉最近最少使用的数据。
我们不妨先思考一下,什么样的数据结构可以满足上述条件,对我们可以使用队列,最近使用的数据放在队尾,淘汰数据时淘汰掉队头的数据即可,我们可以使用一个双向链表来表示成一个队列。
LRU实现方法一
很巧妙的是JAVA中有一个类可以完美契合LRU算法,那就是LinkedHashMap,我们可以使用LinkedHashMap来实现LRU。
代码如下:
class LRUCache<K,V> extends LinkedHashMap<K,V>{
private final int CACHESIZE;
public LRUCache(int size){
super(size,0.75f,true);
CACHESIZE = size;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size()>CACHESIZE;
}
}
自定义一个类继承LinkedHashMap,在构造函数中传入最大缓存值(即内存中允许存放的最多元素个数)。
我们可以看到在构造函数中调用了父类的构造方法,第一个参数就是最大缓存值,第二个参数大家应该都很常见就是负载因子(扩容时使用的),第三个参数的话有两个选择分别为true和false。
-
当传入的是true时,LinkedHashMap中存放元素的顺序是按照最近访问的顺序存放的(完美契合LRU)
-
当传入的是false时,LinkedHashMap中存放元素的顺序是按照元素添加的顺序存放到
removeEldestEntry方法:这个方法就是淘汰元素的方法了,淘汰的条件就是存放元素的个数大于我们指定的最大元素个数后进行淘汰
LRU实现方法二
上面实现的方法就是调用了API实现的,现在我们自己实现一下LRU。
在实现之前先确定几个关键点,存放数据的队列,用双向链表实现,但是双向链表查找数据的效率是非常低的,因此我们可以引入HashMap来帮我们多存放一份数据,这样在查找起来效率就会变高了。
class Node<K,V>{
K k;
V v;
Node next;
Node prev;
public Node(){
next=prev=null;
}
public Node(K k,V v){
this.k = k;
this.v = v;
next=prev=null;
}
}
class DoubleList{
Node head;
Node tail;
public DoubleList(){
head=new Node();
tail=new Node();
head.next=tail;
tail.prev=head;
}
//将数据添加到队尾
public void addLast(Node node){
node.next=tail;
node.prev=tail.prev;
tail.prev.next=node;
tail.prev=node;
}
//删除队列中的数据
public void removeNode(Node node){
node.prev.next=node.next;
node.next.prev=node.prev;
}
}
class LRUCache {
private DoubleList doubleList;
private Map<Integer,Node<Integer,Integer>> map;
private final int CACHESIZE;
public LRUCache(int size){
CACHESIZE=size;
doubleList=new DoubleList();
map=new HashMap<>();
}
public void put(int key,int value){
Node<Integer, Integer> node = map.get(key);
if (node==null){
//添加数据,添加到队尾
Node<Integer,Integer> node1=new Node<>(key,value);
map.put(key,node1);
doubleList.addLast(node1);
}else {
//之前有数据,说明需要移动该数据在队列中的位置
//先删除掉旧数据
doubleList.removeNode(node);
node.v=value;
//将数据添加到队头
doubleList.addLast(node);
}
//判断存放元素大小是否超出阈值
if (map.size()>CACHESIZE){
//超过阈值,淘汰掉队头数据即最近最少使用的数据
Node head = doubleList.head.next;
doubleList.removeNode(head);
//同时将map中对应数据清除掉
map.remove(head.k);
}
}
public int get(int key){
Node<Integer, Integer> node = map.get(key);
if (node==null){
throw new RuntimeException("Key不存在");
}
//使用过该数据则需要将数据移动到队尾
doubleList.removeNode(node);
doubleList.addLast(node);
return node.v;
}
public Set<Integer> keySet(){
return map.keySet();
}
}
以上就是LRU的实现啦,其中关键点就是每次使用数据过后,将数据移动到队尾,淘汰数据时淘汰队头数据就可以啦。
我们来看一下运行结果:
public static void main(String[] args) {
LRUCache lru=new LRUCache(2);
lru.put(1,3);
lru.put(2,4);
lru.get(1);
lru.put(3,5);
System.out.println(lru.keySet());
}
可以看到,最终淘汰的是key为2的数据,也就是淘汰最近最少使用的数据啦
总结
至此可以看出LRU算法还是非常巧妙的一种算法,理解了它的原理后手写出来其实并不难,希望大家都可以掌握~
原文始发于微信公众号(GuoCoding):LRU没想象中那么难
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/42983.html