LRU 的全称是 Least Recently Used(最近最少使用),也就是说我们认为最近使用过的数据应该是是「有用的」,很久都没用过的数据应该是无用的,内存满了就优先删那些很久没用过的数据。
[LeetCode LRU题](146. LRU 缓存 - 力扣(LeetCode) (leetcode-cn.com))
题目要求:
- 题目要求让put 和 get 方法的时间复杂度为 O(1)
- 要求能快速插入删除
数据结构:HashMap 可以快速查找 O(1)时间,链表可以快速删除、插入。结合成哈希链表:LinkedHashMap
在哈希链表中,首部的元素即最少使用的,需要删除的,从尾部添加最近使用的。
- get 分析:get一次,就需要把元素放在尾部
- put 分析:put最新的元素放在尾部,如果大小超过设置的阈值,需要删除首部元素
三种方法实现:
- 自定义哈希链表
- 使用LinkedHashMap
- 继承自 LinkedHashMap
方法一:
// 节点
class Node {
public int key, val;
public Node next, pre;
public Node(int key, int val) {
this.key = key;
this.val = val;
}
}
// 双链表
class DoubleList {
private Node head, tail;
private int size;
public DoubleList() {
head = new Node(-1, -1);
tail = new Node(-1, -1);
// 双链表,注意不是循环链表
head.next = tail;
tail.pre = head;
size = 0;
}
// 由于只需要从末尾添加,从头删除,所以只实现addLast和removeFirst方法
public void addLast(Node x) {
Node pre = tail.pre;
pre.next = x;
x.next = tail;
x.pre = pre;
tail.pre = x;
// 不要忘记了size++
size++;
}
public Node removeFirst() {
if (head.next == tail) return null;
Node first = head.next;
remove(first);
return first;
}
// 删除节点。前提:这个节点是链表中必定有的。
public void remove(Node x) {
x.pre.next = x.next;
x.next.pre = x.pre;
// 不要忘记了size--
size--;
}
public int size() {
return size;
}
}
class LRUCache {
private HashMap<Integer, Node> map;
private DoubleList cache;
private int cap;
public LRUCache(int capacity) {
cap = capacity;
map = new HashMap<>();
cache = new DoubleList();
}
public int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
makeRecently(key);
return map.get(key).val;
}
public void put(int key, int value) {
if (map.containsKey(key)) {
// 删除旧的,添加新的
deleteKey(key);
addRecently(key, value);
return;
}
if (cap == cache.size()) {
removeLeastRecently();
}
addRecently(key, value);
}
/* 为了防止删除或添加时忘记同时操作cache和map,实现以下方法 */
/* 将某个 key 提升为最近使用的 */
private void makeRecently(int key) {
Node x = map.get(key);
// 先从链表中删除这个节点
cache.remove(x);
// 重新插到队尾
cache.addLast(x);
}
/* 添加最近使用的元素 */
private void addRecently(int key, int val) {
Node x = new Node(key, val);
cache.addLast(x);
map.put(key, x);
}
/* 删除某一个 key */
private void deleteKey(int key) {
Node x = map.get(key);
cache.remove(x);
map.remove(key);
}
/* 删除最久未使用的元素 */
private void removeLeastRecently() {
// 链表头部的第一个元素就是最久未使用的
Node x = cache.removeFirst();
// 同时别忘了从 map 中删除它的 key
int key = x.key;
map.remove(key);
}
}
方法二:
class LRUCache {
LinkedHashMap<Integer, Integer> cache;
int cap;
public LRUCache(int capacity) {
cap = capacity;
cache = new LinkedHashMap<>();
}
public int get(int key) {
if (cache.containsKey(key)) {
makeRecent(key);
return cache.get(key);
}
return -1;
}
public void put(int key, int value) {
if (cache.containsKey(key)) {
cache.put(key, value);
makeRecent(key);
return;
}
if (cache.size() >= cap) {
int oldKey = cache.keySet().iterator().next();
cache.remove(oldKey);
}
cache.put(key, value);
}
public void makeRecent(int key) {
Integer val = cache.get(key);
cache.remove(key);
cache.put(key, val);
}
}
方法三:
class LRUCache extends LinkedHashMap<Integer, Integer> {
int cap;
public LRUCache(int capacity) {
super(capacity, 0.75F, true);
this.cap = capacity;
}
public int get(int key) {
return super.getOrDefault(key, -1);
}
public void put(int key, int value) {
super.put(key, value);
}
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size()>cap;
}
}