LRU(least recently use)最少最近使用算法,是缓存淘汰中常用的算法,实际使用要求是:
1.把最常使用的放前面,不常使用的放后面,至于这里的前面和后面会在后面说到。
2.要求存取的时间复杂度是O(1)。
3.存数据的时候,如果容量已满,则去除最后不常使用的数据,再把新数据插入,插入的数据位于前面。如果有该数据则移动到前面。
4.取数据的时候,没有则返回-1,有则返回该数据,并将该数据放在前面。
这里借助hash表和双链表来实现,因为要求时间复杂度是常数级,所以使用hash表存取数,要实现快速移动数据,这里采用双链表,不能采用单链表,单链表不能获得前驱和后继结点指针,必须遍历获得。
而双链表本身结点就保存所以不需要遍历。借助这样的数据结构我们可以知道上文说的前面其实就是链表头,后面就是链表尾。
代码如下所示:
package com.algorithm;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;
/**
* LRU-least recently use:最少最近使用
* 1.get(key),有则返回对应的value值,否则返回-1,将该节点移动到双链表的头部。
* 2.put(key,value),如果双链表容量满了,则移除链表尾部节点,将新节点放到头结点位置,如果没有满,直接放到头部位置。
* 3.要求时间复杂度为O(1)./
*/
public class LRUCache {
public static void main(String[] args) {
LRUCache cache = new LRUCache(3);
cache.set("a", 1);
// System.out.println(cache.get("a"));
// System.out.println(cache.get("b"));
cache.set("b", 2);
cache.set("c", 3);
cache.pirnt();
cache.set("d", 4);
cache.pirnt();
cache.get("b");
cache.pirnt();
// System.out.println(cache.get("a"));
}
int capacity;
Map<String, DoubleLinkedListNode> container;
DoubleLinkedListNode head, tail;
public LRUCache(int capacity) {
this.capacity = capacity;
this.container = new HashMap<>(capacity);
head = new DoubleLinkedListNode();
head.pre = null;
tail = new DoubleLinkedListNode();
tail.next = null;
head.next = tail;
tail.pre = head;
}
public void pirnt() {
DoubleLinkedListNode node = head;
StringJoiner stringJoiner = new StringJoiner("-");
while (node.next != null) {
if (node.next.value!=null){
stringJoiner.add(node.next.value.toString());
}
node = node.next;
}
System.out.println(stringJoiner.toString());
}
public void set(String key, Integer value) {
DoubleLinkedListNode node = this.container.get(key);
if (node == null) {//新节点
node = new DoubleLinkedListNode();
node.key = key;
node.value = value;
if (container.size() >= capacity) {
System.out.println("双向链表已满,移除尾部元素!");
DoubleLinkedListNode del = removeTail();
this.container.remove(del.key);
}
this.container.put(key, node);
addFromHead(node);
} else {//老节点,移动到头部
moveToHead(node);
}
}
public Integer get(String key) {
DoubleLinkedListNode node = this.container.get(key);
if (node == null) {
return -1;
}
moveToHead(node);
return node.value;
}
private void moveToHead(DoubleLinkedListNode node) {
removeNode(node);
addFromHead(node);
}
private DoubleLinkedListNode removeTail() {
DoubleLinkedListNode node = tail.pre;
removeNode(node);
return node;
}
private void removeNode(DoubleLinkedListNode node) {
node.pre.next = node.next;
node.next.pre = node.pre;
}
private void addFromHead(DoubleLinkedListNode cur) {
cur.pre = head;
cur.next = head.next;//下面代码的顺序一定不能颠倒,必须要对,否则指向会出问题。
head.next.pre = cur;
head.next = cur;
}
// 循环链表
class DoubleLinkedListNode {
String key;
Integer value;
DoubleLinkedListNode pre;
DoubleLinkedListNode next;
}
}
特别需要注意的是:在插入元素的时候,双链表指针的移动需要特别注意操作顺序,代码中已经特别标明: