缓存相关知识
命中率:当某个请求能够通过访问缓存而得到响应时,称为缓存命中。
缓存命中率越高,缓存的利用率也就越高。
最大空间:缓存通常位于内存中,内存的空间通常比磁盘空间小的多,因此缓存的最大空间不可能非常大。
当缓存存放的数据量超过最大空间时,就需要淘汰部分数据来存放新到达的数据。
淘汰策略:
- FIFO(First In First Out):先进先出策略,在实时性的场景下,需要经常访问最新的数据,那么就可以使用 FIFO,使得最先进入的数据(最晚的数据)被淘汰。
- LRU(Least Recently Used):最近最久未使用策略,优先淘汰最久未使用的数据,也就是上次被访问时间距离现在最久的数据。该策略可以保证内存中的数据都是热点数据,也就是经常被访问的数据,从而保证缓存命中率。
- LFU(Least Frequently Used):最不经常使用策略,优先淘汰一段时间内使用次数最少的数据。
LRU算法
基于双向链表+HashMap的LRU算法。
- 访问某个节点时,将其从原来的位置删除,并重新插入到链表头部。这样就能保证链表尾部存储的就是最近最少使用的节点,当节点数量大于缓存最大空间时就淘汰链表尾部的节点。
- 为了使删除操作时间复杂度为 O(1),就不能采用遍历的方式找到某个节点。HashMap 存储着 Key 到节点的映射,通过 Key 就能以 O(1) 的时间得到节点,然后再以 O(1) 的时间将其从双向队列中删除。
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;
public class LRU<K, V> implements Iterable<K> {
private Node head;
private Node tail;
private HashMap<K, Node> hashMap;
private final int MAX_CACHE_SIZE;
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) {
MAX_CACHE_SIZE = maxSize;
hashMap = 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 (!hashMap.containsKey(key)) {
return null;
}
Node node = hashMap.get(key);
unlink(node);
appendHead(node);
return node.v;
}
public void put(K key, V value) {
if (hashMap.containsKey(key)) {
Node node = hashMap.get(key);
unlink(node);
}
Node node = new Node(key, value);
hashMap.put(key, node);
appendHead(node);
if (hashMap.size() > MAX_CACHE_SIZE) {
Node toRemove = removeTail();
hashMap.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;
}
};
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int capacity = Integer.valueOf(scanner.nextLine().trim());
LRU lru = new LRU(capacity);
while (scanner.hasNextLine()) {
String command = scanner.nextLine().trim();
if (capacity > 0 && command.charAt(0) == 'p') {
int key = Integer.valueOf(command.substring(2, command.lastIndexOf(" ")));
int value = Integer.valueOf(command.substring(command.lastIndexOf(" ") + 1));
lru.put(key, value);
} else if (command.charAt(0) == 'g') {
if (capacity <= 0)
System.out.println(-1);
else {
int key = Integer.valueOf(command.substring(2));
System.out.println(lru.get(key));
}
}
}
}
}