LRU 缓存
设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值), 并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。
思路
哈希表 + 双向链表
LRU 缓存机制可以通过哈希表辅以双向链表实现,我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。
- 双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。
- 哈希表即为普通的哈希映射,通过缓存数据的键映射到其在双向链表中的位置。
这样一来,我们首先使用哈希表进行定位,找出缓存项在双向链表中的位置,随后将其移动到双向链表的头部,即可在 O(1) 的时间内完成 get 或 put 操作。
- get 操作
- 如果 key 不存在,则返回 -1
- 如果 key 存在,则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。
- put 操作
- key 不存在,使用 key 和 value 创建一个新的节点,在双向链表的头部添加该节点,并将 key 和该节点添加进哈希表中。然后判断双向链表的节点数是超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;
- key 存在,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移动到双向链表的头部。
小贴士
在双向链表的实现中,使用伪头部和伪尾部标记界限,这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在。
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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
| public class LRUCache<K, V> {
private int size; private final int capacity;
private final Node<K, V> head; private final Node<K, V> tail;
private final Map<K, Node<K, V>> CACHE;
public LRUCache(int capacity) { this.capacity = capacity; head = new Node<>(); tail = new Node<>();
head.next = tail; tail.prev = head; this.CACHE = new HashMap<>(capacity); }
public V get(K k) { if (!CACHE.containsKey(k)) { return null; } Node<K, V> node = CACHE.get(k); remove(node); moveToHead(node); return node.value; }
public void put(K k, V v) { if (CACHE.containsKey(k)) { Node<K, V> node = CACHE.get(k); node.value = v; remove(node); moveToHead(node); } else { Node<K, V> node = new Node<>(); node.key = k; node.value = v;
moveToHead(node); CACHE.put(k, node); size++; if (size > capacity) { Node<K, V> tailNode = removeTail(); CACHE.remove(tailNode.key); size--; } } }
private Node<K,V> removeTail() { Node<K, V> node = tail.prev; node.prev.next = tail; tail.prev = node.prev; return node; }
private void moveToHead(Node<K,V> node) { node.next = head.next; head.next.prev = node; head.next = node; node.prev = head; }
private void remove(Node<K,V> node) { node.prev.next = node.next; node.next.prev = node.prev; }
private static class Node<K, V> { private K key; private V value; private Node<K, V> prev; private Node<K, V> next;
}
public static void main(String[] args) { LRUCache<Integer, Integer> cache = new LRUCache( 2 );
cache.put(1, 1); cache.put(2, 2); System.out.println(cache.get(1)); cache.put(3, 3); System.out.println(cache.get(2)); cache.put(4, 4); System.out.println(cache.get(1)); System.out.println(cache.get(3)); System.out.println(cache.get(4)); } }
|