LRU 缓存机制及 3 种简单实现
之前好几次接触到 LRU(Least Recently Used)算法,今天来总结下,并用 Java 和 Python 给出相应的实现。
LRU是一种缓存替换算法,根据字面意思,就是将最近最少使用的页面或者元素进行替换,将最近最多使用的页面或者元素保持在缓存里。有关缓存的知识后面再仔细研究下。由于缓存的容量大小有限,这才有了LRU之类的缓存算法。还有一些其他的缓存算法,可以参考这个页面。
根据下面的图示进行LRU算法的理解。
其中 put 操作用于将最近使用的元素放置在缓存中。必须先判断 key 是否存在,如果存在,则删除,再添加;若不存在,则直接添加,然后判断添加后的缓存是否超过了容量;若超出,则删除最远元素;get 操作用于获取缓存中元素的值,在 leetcode146 题中规定,如果缓存中没有该元素,则返回 -1。
一般我们在实现的时候会考虑存储 key-value 的键值对形式,可以用双链表存储 key,HashMap 存储真正需要的值 value,所以真正意义上的缓存应该是指这个HashMap。链表的作用是用来顺序存储 key,当缓存满了,需要删除最远的那个 key 及其 value,此时就需要根据链表找到最远的 value 的 key,从而删除缓存 HashMap中的最远的键值对。
这里我们用 双链表 + hashMap 以及 LinkedHashMap 、Python 中 OrderedDict 三种方式来实现一个简单的 LRU 机制。
双链表 + hashMap
1 class Solution {
2 private LinkedList<Integer> linkedList;
3 private Map<Integer, Integer> map;
4
5 private int max_size;
6 private int cur_size = 0;
7
8 public Solution(int capacity) {
9 linkedList = new LinkedList<>();
10 map = new HashMap<>();
11 this.max_size = capacity;
12 }
13
14 public int get(int key) {
15 if(!map.containsKey(key)){
16 return -1;
17 }
18
19 int val = map.get(key);
20 Object o = key;
21 linkedList.remove(o);
22 linkedList.addLast(key);
23 return val;
24 }
25
26 public void put(int key, int value) {
27 if(map.containsKey(key)){
28 // 这个put不能省略,即时key存在,若新添加的value更新了,那刚好就将value更新,如果省略,则value更新不了
29 map.put(key, value);
30 Object o = key;
31 linkedList.remove(o);
32 linkedList.addLast(key);
33 }else{
34 map.put(key, value);
35 cur_size++;
36 linkedList.addLast(key);
37 if(cur_size>max_size){
38 int tmp = linkedList.removeFirst();
39 map.remove(tmp);
40 cur_size--;
41 }
42 }
43 }
44 }
其中在进行元素删除的时候,链表的时间复杂度是O(n),用 HashMap 进行 key 的查找的时候是O(1)的复杂度。
LinkedHashMap
1 class Solution {
2 private LinkedHashMap<Integer, Integer> map;
3 private int max_size;
4 private int cur_size;
5
6 public Solution(int capacity) {
7 map = new LinkedHashMap<>();
8 this.max_size = capacity;
9 this.cur_size = 0;
10 }
11
12 public int get(int key) {
13 // 若没有,则返回 -1
14 if(!map.containsKey(key)){
15 return -1;
16 }
17
18 int val = map.get(key);
19 map.remove(key);
20 map.put(key, val);
21 return val;
22 }
23
24 public void put(int key, int value) {
25 if(map.containsKey(key)){
26 map.remove(key);
27 map.put(key, value);
28 }else{
29 cur_size++;
30 map.put(key, value);
31 if(cur_size > max_size){
32 int oldestKey = map.keySet().iterator().next(); // 获取最远的key。
33 map.remove(oldestKey);
34 cur_size--;
35 }
36 }
37 }
38 }
LinkedHashMap 本身也是由 双链表 + hashMap 组成的。在缓存满了需要删除最远的元素的时候,是用的 HashMap 里的迭代器来获取最开始进来的key并删除其键值对。
Python OrderedDict
Python 可以使用这篇文章介绍的 OrderedDict 这一字典子类很轻松的实现 LRU 机制。
1 class LRUCache:
2
3 def __init__(self, capacity: int):
4 self.dic = OrderedDict()
5 self.remain = capacity
6
7
8 def get(self, key: int) -> int:
9 if key not in self.dic:
10 return -1
11 # v = self.dic.pop(key)
12 # self.dic[key] = v
13 self.dic.move_to_end(key, last = True)
14 return self.dic[key]
15
16 def put(self, key: int, value: int) -> None:
17 if key in self.dic:
18 self.dic.pop(key)
19 else:
20 if self.remain > 0:
21 self.remain -= 1
22 else:
23 self.dic.popitem(last = False)
24 self.dic[key] = value