The topic comes from LeetCode
146. LRU caching mechanismOther solutions or source code can be accessed: tongji4m3
description
Use the data structure you have mastered to design and implement an LRU (least recently used) cache mechanism. It should support the following operations: get data get and write data put.
Get data get(key)-If the key (key) exists in the cache, get the value of the key (always a positive number), otherwise return -1.
Write data put(key, value)-If the key already exists, change its data value; if the key does not exist, insert the set of "key/value". When the cache capacity reaches the upper limit, it should delete the longest unused data value before writing new data to make room for the new data value.
Advanced:
Can you complete these two operations in O(1) time complexity?
Example:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得关键字 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得关键字 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
Ideas
- Can be used
LinkedHashMap
, the method has been encapsulated, just rewrite theremoveEldestEntry
method - To implement the logic by yourself, use the combination of a doubly linked list and a hash table, which is actually the specific implementation of the above method 1.
detail
Code
public class LRUCache
{
private Map<Integer, Node> map;//存储的是key,Node
DoubleList cache;//用于保证删除是O(1)
private int capacity;
private class Node
{
int key;
int value;
Node next;
Node pre;
Node(int key, int value)
{
this.key = key;
this.value = value;
}
}
private class DoubleList
{
Node first;//头尾虚节点
Node last;
int size;
DoubleList()
{
first = new Node(0, 0);
last = new Node(0, 0);
first.next = last;
last.pre = first;
size = 0;
}
void addFirst(Node node)
{
node.pre=first;
node.next=first.next;
first.next.pre=node;
first.next=node;
++size;
}
Node remove(Node node)
{
node.pre.next = node.next;
node.next.pre = node.pre;
node.pre = null;
node.next = null;
--size;
return node;
}
Node removeLast()
{
if(size==0) return null;
return remove(last.pre);
}
}
public LRUCache(int capacity)
{
this.map = new HashMap<>();
this.cache = new DoubleList();
this.capacity = capacity;
}
public int get(int key)
{
if (!map.containsKey(key)) return -1;
int value = map.get(key).value;
put(key, value);
return value;
}
public void put(int key, int value)
{
Node node = new Node(key, value);
if (map.containsKey(key))
{
cache.remove(map.get(key));
cache.addFirst(node);
}
else
{
if (capacity == cache.size)
{
Node last = cache.removeLast();
map.remove(last.key);//这是为什么Node要存key,val,不能只存val
}
cache.addFirst(node);
}
map.put(key, node);
}
}
/**
调用库的实现方法
LinkedHashMap<Integer, Integer>
它是一个将所有Entry节点链入一个双向链表的HashMap
此外,LinkedHashMap可以很好的支持LRU算法
它额外维护了一个双向链表用于保持迭代顺序,该迭代顺序可以是插入顺序,也可以是访问顺序。
* @author 12549
*/
public class LRUCache extends LinkedHashMap<Integer, Integer>
{
private Integer capacity;
/*
当accessOrder标志位为true时,表示双向链表中的元素按照访问的先后顺序排列
当标志位accessOrder的值为false时,表示双向链表中的元素按照Entry插入LinkedHashMap到中的先后顺序排序
当我们要用LinkedHashMap实现LRU算法时,就需要调用该构造方法并将accessOrder置为true。
当accessOrder为true时,get方法和put方法都会调用recordAccess方法使得最近使用的Entry移到双向链表的末尾
*/
public LRUCache(int capacity)
{
super(capacity,0.75F, true);
this.capacity = capacity;
}
public int get(int key)
{
return super.getOrDefault(key, -1);
}
public void put(int key, int value)
{
super.put(key, value);
}
/*
该方法是用来被重写的,一般地,如果用LinkedHashMap实现LRU算法,就要重写该方法。
比如可以将该方法覆写为如果设定的内存已满,则返回true,
这样当再次向LinkedHashMap中putEntry时,
在调用的addEntry方法中便会将近期最少使用的节点删除掉(header后的那个节点)。
*/
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest)
{
return size()>capacity;
}
}