什麼是緩存淘汰策略?
緩存位於內存中,而內存的空間很有限,所以緩存也有一個能使用的最大空間,當緩存中的數據超過這個最大空間時,就要使用緩存淘汰策略淘汰一些數據,空出空間給其他數據使用。
最近最久未使用策略
最近最久未使用策略,優先淘汰最久未使用的數據,也就是上次被訪問時間距離現在最久的數據。該策略可以保證內存中的數據都是熱點數據,也就是經常被訪問的數據,從而保證緩存命中率。
實現方式
以下是基於 雙向鏈表 + HashMap 的 LRU 算法實現,對算法的解釋如下:
- 訪問某個節點時,將其從原來的位置刪除,並重新插入到鏈表頭部。這樣就能保證鏈表尾部存儲的就是最近最久未使用的節點,當節點數量大於緩存最大空間時就淘汰鏈表尾部的節點。
- 爲了使刪除操作時間複雜度爲 O(1),就不能採用遍歷的方式找到某個節點。HashMap 存儲着 Key 到節點的映射,通過 Key 就能以 O(1) 的時間得到節點,然後再以 O(1) 的時間將其從雙向隊列中刪除。
public class LRU<K, V> implements Iterable<K> {
private Node head;
private Node tail;
private HashMap<K, Node> map;
private int maxSize;
private class Node {
Node pre;
Node next;
K k;
V v;
public Node(K k, V v) {
this.k = k;
this.v = v;
}
}
public LRU(int maxSize) {
this.maxSize = maxSize;
this.map = new HashMap<>(maxSize * 4 / 3);
head = new Node(null, null);
tail = new Node(null, null);
head.next = tail;
tail.pre = head;
}
public V get(K key) {
if (!map.containsKey(key)) {
return null;
}
Node node = map.get(key);
unlink(node);
appendHead(node);
return node.v;
}
public void put(K key, V value) {
if (map.containsKey(key)) {
Node node = map.get(key);
unlink(node);
}
Node node = new Node(key, value);
map.put(key, node);
appendHead(node);
if (map.size() > maxSize) {
Node toRemove = removeTail();
map.remove(toRemove.k);
}
}
private void unlink(Node node) {
Node pre = node.pre;
Node next = node.next;
pre.next = next;
next.pre = pre;
node.pre = null;
node.next = null;
}
private void appendHead(Node node) {
Node next = head.next;
node.next = next;
next.pre = node;
node.pre = head;
head.next = node;
}
private Node removeTail() {
Node node = tail.pre;
Node pre = node.pre;
tail.pre = pre;
pre.next = tail;
node.pre = null;
node.next = null;
return node;
}
@Override
public Iterator<K> iterator() {
return new Iterator<K>() {
private Node cur = head.next;
@Override
public boolean hasNext() {
return cur != tail;
}
@Override
public K next() {
Node node = cur;
cur = cur.next;
return node.k;
}
};
}
}