什么是LRU
LRU全称是Least Recently Used,即最近最久未使用的意思
LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰
图示
映射关系
增删改
代码问题解释
我们基本实现原理是通过Hashmap+双链表(Entry)实现的.
- 为什么用hashmap+双链表,而不是单独的双链表?
- 原因:用hashmap找双链表比较快,容易定位到双链表的具体的单节点,进行操作;而不是每次修改或者删除的时候都需要遍历整条链表
- 为什么定义head节点和tail节点?
- 原因:用head节点和tail节点更容易操作,尤其是在删除的时候
具体实现方法
-
首先需要编写一个Entry类,Entry类里面含有key,value值,还有前驱结点pre和后继节点next,重写构造函数
-
其次,在我们的LRUCache类定义一个head节点和tail节点,以及int一个容量池大小capacity和记录我们的链表的长度size,还有一个Hashmap<Integer,Entry>的cache
-
我们定义一个initLinkedList()方法,初始化我们的链表,使头尾节点相连
-
重写LRUCache的构造函数,赋值capacity并定义Hashmap容量池大小,size=0,调用initLinkedList()方法
-
定义addNode()和deleteNode()方法,每次添加节点都添加到头结点之后,删除的话让该几点的前后节点相连
-
定义moveToHead()方法,里面包裹着deleteNode()和addNode()方法,方便我们修改已存在的值(因为我们修改已存在的值的时候,需要删除该节点,然后在添加该节点)
-
定义get()方法,从hashmap表里查找该key存不存在,如果存在,就调用moveToHead()方法移至头结点,不存在就返回null
-
接下来定义put()方法,这个比较难,分情况
-
首先我们先get()一下,查看有没有该Entry节点
-
如果有该Entry节点,那么我们就重新赋该Entry节点的值,调用moveToHead()方法,返回,结束方法
-
如果没有该Entry节点,就new一个新的Entry节点,赋值key,value,调用addNode()方法,并放入Hashmap,size++
-
如果size==capacity,也就是链表长度已满,把最后一个节点(不是tail节点,是tail.pre)删掉(因为最后一个节点不常用,所以删掉),并把Hashmap中的该节点也删掉,size--,之后执行上面第2部操作
-
-
-
测试得出以下语句
public static void main(String[] args) {
LRUCache cache1 = new LRUCache(2);
cache1.put(1, 1);
cache1.put(2, 2);
System.out.println("get(1)之后的头结点之前的节点: " + cache1.head.next);
System.out.println("get(1)节点: " + cache1.get(1));
System.out.println("get(1)之后的头结点之后的节点: " + cache1.head.next);
cache1.put(3, 3);
System.out.println("put(3)之后的头结点之后的节点: " + cache1.head.next);
System.out.println("get(2)节点: " + cache1.get(2));
System.out.println("get(2)之后的头结点之后的节点: " + cache1.head.next);
}
//最终输出
//get(1)之后的头结点之前的节点: Entry{key=2, value=2}
//get(1)节点: 1
//get(1)之后的头结点之后的节点: Entry{key=1, value=1}
//put(3)之后的头结点之后的节点: Entry{key=3, value=3}
//get(2)节点: null
//get(2)之后的头结点之后的节点: Entry{key=3, value=3}
源码
package com.heaboy.mvc;
import java.util.HashMap;
import java.util.Map;
public class LRUCache {
Entry head, tail;
int capacity;
int size;
Map<Integer, Entry> cache;
public LRUCache(int capacity) {
this.capacity = capacity;
initLinkedList();
size = 0;
cache = new HashMap<>(capacity);
}
public void put(int key, int value) {
Entry node = cache.get(key);
if (node != null) {
node.value = value;
moveToHead(node);
return;
}
if (size == capacity) {
Entry lastNode = tail.pre;
deleteNode(lastNode);
cache.remove(lastNode.key);
size--;
}
Entry newNode = new Entry(key, value);
addNode(newNode);
cache.put(key, newNode);
size++;
}
public Integer get(int key) {
Entry node = cache.get(key);
if (node == null) {
return null;
}
moveToHead(node);
return node.value;
}
private void moveToHead(Entry node) {
deleteNode(node);
addNode(node);
}
public void addNode(Entry node) {
head.next.pre = node;
node.next = head.next;
node.pre = head;
head.next = node;
}
private void deleteNode(Entry node) {
node.pre.next = node.next;
node.next.pre = node.pre;
}
private void initLinkedList() {
head = new Entry();
tail = new Entry();
head.next = tail;
tail.pre = head;
}
private static class Entry {
int key;
int value;
Entry pre;
Entry next;
public Entry(int key, int value) {
this.key = key;
this.value = value;
}
public Entry() {
}
@Override
public String toString() {
return "Entry{" +
"key=" + key +
", value=" + value +
'}';
}
}
public static void main(String[] args) {
LRUCache cache1 = new LRUCache(2);
cache1.put(1, 1);
cache1.put(2, 2);
System.out.println("get(1)之后的头结点之前的节点: "+cache1.head.next);
System.out.println("get(1)节点: "+cache1.get(1));
System.out.println("get(1)之后的头结点之后的节点: "+cache1.head.next);
cache1.put(3, 3);
System.out.println("put(3)之后的头结点之后的节点: "+cache1.head.next);
System.out.println("get(2)节点: "+cache1.get(2));
System.out.println("get(2)之后的头结点之后的节点: "+cache1.head.next);
}
}