https://labuladong.online/algo/data-structure/lru-cache/
LRU算法是一种常见的缓存淘汰策略,全称是Least Recently Used, 很久没使用的应该是无用的数据应该删除
LRU算法的核心数据结构是使用LinkedHashMap,首先借助链表的有序性使得插入的元素还是保持有序的状态,同时借助哈希映射的快速访问能力,O(1)任意元素。
1、算法的描述
首先要接收一个 capacity 参数作为缓存的最大容量,然后实现两个 API,一个是 put(key, val) 方法存入键值对,另一个是 get(key) 方法获取 key 对应的 val,如果 key 不存在则返回 -1。
注意哦,get 和 put 方法必须都是 O(1)的时间复杂度,我们举个具体例子来看看 LRU 算法怎么工作。
LRU 缓存算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体
1、显然操作的元素必须有序,用来区分最近使用和最久未使用;
2、我们要才cache中快速找到某个key,得到对应 的val
3、每次访问某个key,必须将这个元素变为最近使用,也就是支持任意位置插入和查找;
2、代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 from numpy.ma.core import swapaxesclass Node : def __init__ (self, key, value ): self .key = key self .value = value self .next = None self .prev = None class DoubleLinkedList : def __init__ (self ): """ head tail [0 | 0 ] -> [0 | 0 ] ->> head pre next -> pre next """ self .head = Node(0 , 0 ) self .tail = Node(0 , 0 ) self .head.next = self .tail self .tail.next = self .head self .size = 0 def addLast (self, x: Node ): """ 在尾部添加节点 head x tail [0 | 0 ] -> [0 | 0 ] -> [0 | 0 ] ->> head pre next -> pre next -> pre next ->> head """ x.prev = self .tail.prev x.next = self .tail self .tail.prev = x self .tail.prev.next = x self .size += 1 def remove (self, x: Node ): """ 移除某个节点,O(1) head x tail [0 | 0 ] -> [0 | 0 ] -> [0 | 0 ] ->> head pre next -> pre next -> pre next ->> head """ x.prev.next = x.next x.next .prev = x.prev self .size -= 1 def removeFirst (self ): """ 删除链表的第一个节点,O(1) """ if self .head.next == self .tail: return None x = self .head.next self .remove(x) return x def length (self ): """ 返回链表的长度 """ return self .size class LRUCache : def __init__ (self, capacity ): self .capacity = capacity self .cache = DoubleLinkedList() self .map = {} def addRecently (self, key, value ): x = Node(key, value) self .cache.addLast(x) self .map [key] = x def makeRecently (self, key ): """ 将数据提升为最近使用的 """ x = self .map .get(key) self .cache.remove(x) self .cache.addLast(x) def removeLeastRecently (self ): """ 链表的头就是最久未使用的 """ deleteNode = self .cache.removeFirst() deleteKey = deleteNode.key self .map .pop(deleteKey) def deleteKey (self, key ): x = self .map .get(key) self .cache.remove(x) self .map .pop(key) def get (self, key ): if key not in self .map .keys(): return -1 self .makeRecently(key) return self .map [key].value def put (self, key, value ): if key in self .map .keys(): self .deleteKey(key) self .addRecently(key, value) return if self .capacity == self .capacity: self .removeLeastRecently() self .makeRecently(key)
我们最后用 Java 的内置类型 LinkedHashMap 来实现 LRU 算法,逻辑和之前完全一致:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class LRUCache { int cap; LinkedHashMap<Integer, Integer> cache = new LinkedHashMap <>(); public LRUCache (int capacity) { this .cap = capacity; } public int get (int key) { if (!cache.containsKey(key)) { return -1 ; } makeRecently(key); return cache.get(key); } public void put (int key, int val) { if (cache.containsKey(key)) { cache.put(key, val); makeRecently(key); return ; } if (cache.size() >= this .cap) { int oldestKey = cache.keySet().iterator().next(); cache.remove(oldestKey); } cache.put(key, val); } private void makeRecently (int key) { int val = cache.get(key); cache.remove(key); cache.put(key, val); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 from collections import OrderedDictclass LRU : d = OrderedDict() def __init__ (self, capacity ): self .capacity = capacity def get (self, key ): if key not in self .d: return -1 self .makeRecently(key) return self .d.get(key) def put (self, key, value ): if key in self .d: self .d[key] = value self .makeRecently(key) else : if len (self .d) >= self .capacity: old_key, old_value = self .d.popitem(last=False ) self .d.pop(old_key) self .d[key] = value def makeRecently (self, key ): """ 把这个标记为最近使用的key """ if key not in self .d: return -1 self .d.move_to_end(key) if __name__ == '__main__' : d = OrderedDict() d[1 ] = 1 d[2 ] = 2 d[3 ] = 3 print (d) d.popitem() print (d)
__END__