这是我参与「第三届青训营 -后端场」笔记创作活动的第 2 篇笔记
相关算法题:LRU 缓存 - LeetCode
LRU 和 LFU 两种算法作为缓存淘汰策略在计算机领域的应用都十分广泛,基本上写一个应用层的缓存你就应该支持这两种策略,或至少支持 LRU 算法。Java 其实是自带 LRU 缓存的,继承 LinkedHashMap 做简单的修改就能实现 LRU 缓存。
public LRUCache<K, V> extends LinkedHashMap<K, V> {
private int cacheSize;
public LRUCache(int cacheSize) {
super(16, 0.75, true);
this.cacheSize = cacheSize;
}
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() >= cacheSize;
}
}
如果你觉得调 API 不能展现你的技术水平,那我们试试自己写一个 LRU 缓存吧。
LRU 缓存是淘汰最近最少使用的 key ,使用哈希表和双链表实现 LRU 缓存,只需每次访问都将节点移动到链表尾部,淘汰时淘汰头部节点即可。这个方案 get 和 put 的时间复杂度为 O(1) ,Java 实现如下:
public class LRUCache<K, V> {
static class Node<K, V> {
final K key;
V value;
Node<K, V> pre, next;
Node(K key) {
this.key = key;
}
}
final int capacity;
final Map<K, Node<K, V>> map;
Node<K, V> head;// eldest
Node<K, V> tail;// youngest
public LRUCache(int capacity) {
this.capacity = capacity;
this.map = new HashMap<>(capacity);
}
public V get(K key) {
if (capacity == 0) return null;
Node<K, V> node = map.get(key);
if (node == null) return null;
moveToTail(node);
return node.value;
}
public V put(K key, V val) {
if (capacity == 0) return null;
Node<K, V> node = map.get(key);
V old;
if (node != null) {
old = node.value;
} else {
old = null;
if (map.size() == capacity) {
removeEldest();
}
map.put(key, node = new Node<>(key));
}
node.value = val;
moveToTail(node);
return old;
}
private void moveToTail(Node<K, V> node) {
if (node == tail) return;
if (head == null) {
head = node;
tail = node;
return;
}
Node<K, V> n = node.next;
// unlink
if (node.pre != null) {
node.pre.next = node.next;
}
if (node.next != null) {
node.next.pre = node.pre;
}
node.pre = null;
node.next = null;
if (node == head) {
head = n;
if (head != null) {
head.pre = null;
}
}
// link to tail
tail.next = node;
node.pre = tail;
tail = node;
}
private void removeEldest() {
if (head == null) return;
Node<K, V> h = head;
head = h.next;
h.next = null;
if (head != null) {
head.pre = null;
}
map.remove(h.key);
}
}
如果想让这个 LRU 缓存支持并发操作,可以考虑使用读写锁来保证多线程安全,当然加读写锁并不是最优解,如果追求极致的并发性能就比较复杂了,可以去看看 Caffeine 和 Guava 是如何实现的。