赞
踩
LRU是一种缓存淘汰策略,对于我们的计算机来说,缓存容量是有限的,那么当缓存满了要怎么办?这时就要用到LRU,对于很久没有用过的数据,我们可以将其判定为无用的数据,当新资源进入缓存或者用到了某个数据的时候,对应的资源可以判定为有用的数据,当缓存满了时我们应当优先淘汰无用的数据,而对于最近使用过的数据应当靠后淘汰,这就是LRU算法。
和手机后台一样,我们会优先杀掉自己很久没用的后台,而常用的则不会杀掉它,甚至还会上锁
属性包括key、value、前驱结点和后继节点
public class Node {
int key,val;
Node pre,next;
public Node(int key, int val) {
this.key = key;
this.val = val;
}
}
为了达到有序性,定义头结点head和尾结点tail,因为缓存有容量,所以还有size属性,表示当前链表中元素的数量。我们先不写出最后四个方法,接下来我们会依次讲解这四个方法
public class DoubleList { private Node head,tail; private int size; public DoubleList() { size = 0; } public void addFirst(Node node){} public void romove(Node node){} public Node romoveLast(){} public int size(){} }
该方法的作用是将新数据放到链表的头部,利用头插法,时间复杂度为O(1)
public void addFirst(Node node){ //如果此时链表中没有数据,直接将head指向该结点 if(size == 0){ head = node; }else{ //如果有数据 //使用头插法将该结点放到开头, node.next = head; head = node; //注意这是双向链表,还要管理pre指针 head.next.pre = head; //当链表中已经有一个数据时,我们把tail指针加入进来,指向head的下一个 if(size == 1){ tail = head.next; } } //链表数据量加1 size++; }
①如果没有数据,此时head指向node
②如果已经有一个数据,头插法完毕后,加入tail结点
③如果数据量大于1,正常的头插法
该方法的作用是移除对应的结点,利用next和pre指针,时间复杂度为O(1)
public void romove(Node node){ //如果该结点是尾结点 if(node.next == null && node.pre != null){ //改变尾结点到上一个结点 tail = node.pre; node.pre.next = null; node.pre = null; //如果该结点是头结点 }else if(node.pre == null && node.next != null){ //改变头结点到下一个结点 head = node.next; node.next.pre = null; node.next = null; //如果该结点是中间结点 }else if(node.pre != null && node.next != null){ //改变上个结点的next node.pre.next = node.next; //改变下个结点的pre node.next.pre = node.pre; //如果该结点是链表中唯一个结点 }else{ //直接把head搞成null head = null; } //链表数据量减1 size--; }
该方法的作用是移除最后的结点,相当于淘汰掉缓存中最不常用的数据,时间复杂度为O(1)
return该结点是因为在HashMap中也要移除,所以要确定该结点的key
public Node romoveLast(){ Node node; //如果只有一个数据 if(size == 1){ //记住head结点,然后指向null node = head; head = null; }else{ //如果有多条数据 //记住tail结点并改变tail的位置 node = tail; tail = tail.pre; tail.next = null; } //链表数据量减1 size--; return node; }
为了判断LRUCache是否已满,没什么好说的,直接return size
public int size() {
return size;
}
属性包括了,缓存容量capacity,双向链表list以及哈希表map。对于缓存来说,最基本的方法就是put和get方法,所以我们只要实现这两个方法就可以了,后面来依次讲解
public class LRUCache {
int capacity;
DoubleList list;
HashMap<Integer,Node> map;
//初始化
public LRUCache(int capacity) {
this.capacity = capacity;
list = new DoubleList();
map = new HashMap<>();
}
public void put(int key,int value){}
public int get(int key){}
}
此方法的作用是将相应的数据放入缓存
注意要放到第一位并且要考虑重复和缓存满的情况
public void put(int key,int value){ //new一个Node存放key和value Node node = new Node(key,value); //如果HashMap中存在这个key,表示重复了 if (map.containsKey(key)) { //首先在双向链表中移除这个重复的结点 list.romove(map.get(key)); //然后将新结点放到第一位 list.addFirst(node); //最后覆盖HashMap中的相同key的数据 map.put(key,node); //如果HashMap不存在这个key }else{ //如果缓存已满 if(capacity == list.size()){ //移除最后一个数据,这个数据是很久没有被使用过的 Node last = list.romoveLast(); //HashMap中也要移除 map.remove(last.key); } //将新结点放到第一位 list.addFirst(node); //HashMap中也要存放这个数据 map.put(key,node); } }
此方法的作用是获取某一key值的数据
public int get(int key){
//如果在哈希表中没有这个key,return -1
if (!map.containsKey(key)) {
return -1;
}
//如果存在key,通过HashMap的get()获取到相应的结点并得到val
int res = map.get(key).val;
//因为我们使用到了这个数据,所以要调用put()把这个数据放到第一位
put(key,res);
return res;
}
public class Test {
public static void main(String[] args){
LRUCache cache = new LRUCache(2);
cache.put(1,1);
cache.put(2,2);
cache.put(3,3);
System.out.println(cache.get(1));
System.out.println(cache.get(2));
System.out.println(cache.get(3));
}
}
容量为2,key为1的数据是最不常用的数据,应该淘汰,key为3的数据放入
public class Test {
public static void main(String[] args){
LRUCache cache = new LRUCache(2);
cache.put(1,1);
cache.put(2,2);
cache.put(1,4);
cache.put(3,3);
System.out.println(cache.get(1));
System.out.println(cache.get(2));
System.out.println(cache.get(3));
}
}
容量为2,存在key为1的数据重复,覆盖掉,此时key为2的数据是最不常用的数据,应当淘汰,key为3的数据放入
就简单测试一下吧,更复杂的测试大家可以自己来进行,本人能力有限,可能出现考虑不周的地方,如果遇到不对的地方希望可以多多指正,谢谢大家
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。