文章目录
1. LRU(Least Recently Used)最近最少使用
根据数据最近访问时间淘汰。淘汰的是在所有数据中,访问时间离当前时间最远的数据。
双向链表+哈希表。越靠近表头表示使用的时间最近。每次删除结尾节点。
public class LRUCache {
/**
* 双向链表中的节点
*/
class Node {
Node pre, next;
int value;
int key;
Node(int key, int value) {
this.key = key;
this.value = value;
}
Node() {
}
}
// 头结点
Node head;
// 尾节点
Node tail;
// 链表容量
int capacity;
Map<Integer, Node> maps = new HashMap<>();
/**
* 将双向链表中的节点移动到头结点
*/
private void goHead(Node node) {
node.pre.next = node.next;
node.next.pre = node.pre;
putHead(node);
}
/**
* 删除尾节点
*/
private void removeTail() {
if (head.next == tail) {
return;
}
int key = tail.pre.key;
tail.pre.pre.next = tail;
tail.pre = tail.pre.pre;
maps.remove(key);
capacity++;
}
/**
* 判断当前状态是否能插入,不能则需要淘汰数据
*/
private boolean canPut() {
return capacity > 0;
}
/**
* 将节点放到链表头节点
*/
private void putHead(Node node) {
head.next.pre = node;
node.next = head.next;
head.next = node;
node.pre = head;
}
/**
* 初始化
*/
public LRUCache(int capacity) {
head = new Node();
tail = new Node();
head.next = tail;
tail.pre = head;
this.capacity = capacity;
}
/**
* 从缓存中拿数据,拿不到返回-1.拿到则将其放到队列首
*/
public int get(int key) {
Node node = maps.get(key);
if (node == null) {
return -1;
} else {
goHead(node);
return node.value;
}
}
/**
* 添加缓存。如果添加会超过容量,则需要淘汰数据
*/
public void put(int key, int value) {
Node node = maps.get(key);
if (node != null) {
node.value = value;
goHead(node);
} else {
if (!canPut()) {
removeTail();
}
if (canPut()) {
node = new Node(key, value);
putHead(node);
maps.put(key, node);
capacity--;
}
}
}
public static void main(String[] args) {
LRUCache lruCache = new LRUCache(2);
System.out.println(lruCache.get(2)); // -1
lruCache.put(1, 1);
lruCache.put(2, 2);
System.out.println(lruCache.get(1)); // 1
lruCache.put(3, 3);
System.out.println(lruCache.get(2)); // -1
lruCache.put(4, 4);
System.out.println(lruCache.get(1)); // -1
System.out.println(lruCache.get(3)); // 3
System.out.println(lruCache.get(4)); // 4
}
}
2. LFU(Least Frequently Used)最近最不经常使用
根据数据访问次数淘汰。每次清除使用次数少的数据。可能导致新数据经常被删除。
例题:LFU
class LFUCache {
int minfreq, capacity;
Map<Integer, Node> keyTable;
Map<Integer, DoublyLinkedList> freqTable;
public LFUCache(int capacity) {
this.minfreq = 0;
this.capacity = capacity;
keyTable = new HashMap<Integer, Node>();
freqTable = new HashMap<Integer, DoublyLinkedList>();
}
public int get(int key) {
if (capacity == 0) {
return -1;
}
if (!keyTable.containsKey(key)) {
return -1;
}
Node node = keyTable.get(key);
int val = node.val, freq = node.freq;
freqTable.get(freq).remove(node);
// 如果当前链表为空,我们需要在哈希表中删除,且更新minFreq
if (freqTable.get(freq).size == 0) {
freqTable.remove(freq);
if (minfreq == freq) {
minfreq += 1;
}
}
// 插入到 freq + 1 中
DoublyLinkedList list = freqTable.getOrDefault(freq + 1, new DoublyLinkedList());
list.addFirst(new Node(key, val, freq + 1));
freqTable.put(freq + 1, list);
keyTable.put(key, freqTable.get(freq + 1).getHead());
return val;
}
public void put(int key, int value) {
if (capacity == 0) {
return;
}
if (!keyTable.containsKey(key)) {
// 缓存已满,需要进行删除操作
if (keyTable.size() == capacity) {
// 通过 minFreq 拿到 freqTable[minFreq] 链表的末尾节点
Node node = freqTable.get(minfreq).getTail();
keyTable.remove(node.key);
freqTable.get(minfreq).remove(node);
if (freqTable.get(minfreq).size == 0) {
freqTable.remove(minfreq);
}
}
DoublyLinkedList list = freqTable.getOrDefault(1, new DoublyLinkedList());
list.addFirst(new Node(key, value, 1));
freqTable.put(1, list);
keyTable.put(key, freqTable.get(1).getHead());
minfreq = 1;
} else {
// 与 get 操作基本一致,除了需要更新缓存的值
Node node = keyTable.get(key);
int freq = node.freq;
freqTable.get(freq).remove(node);
if (freqTable.get(freq).size == 0) {
freqTable.remove(freq);
if (minfreq == freq) {
minfreq += 1;
}
}
DoublyLinkedList list = freqTable.getOrDefault(freq + 1, new DoublyLinkedList());
list.addFirst(new Node(key, value, freq + 1));
freqTable.put(freq + 1, list);
keyTable.put(key, freqTable.get(freq + 1).getHead());
}
}
}
class Node {
int key, val, freq;
Node prev, next;
Node() {
this(-1, -1, 0);
}
Node(int key, int val, int freq) {
this.key = key;
this.val = val;
this.freq = freq;
}
}
class DoublyLinkedList {
Node dummyHead, dummyTail;
int size;
DoublyLinkedList() {
dummyHead = new Node();
dummyTail = new Node();
dummyHead.next = dummyTail;
dummyTail.prev = dummyHead;
size = 0;
}
public void addFirst(Node node) {
Node prevHead = dummyHead.next;
node.prev = dummyHead;
dummyHead.next = node;
node.next = prevHead;
prevHead.prev = node;
size++;
}
public void remove(Node node) {
Node prev = node.prev, next = node.next;
prev.next = next;
next.prev = prev;
size--;
}
public Node getHead() {
return dummyHead.next;
}
public Node getTail() {
return dummyTail.prev;
}
}
3. 近似LRU算法
只选择一部分数据进行LRU淘汰,而非在整体上计算需要淘汰哪些数据。
4. TTL(Time To Live)超时时间
设置缓存的过期时间,如果空间已满,则优先淘汰最接近过期时间的数据。
5. FIFO(First In First Out)先进先出
按数据写入顺序淘汰。先写入的会被先淘汰。可能将热点数据淘汰,但实现简单。
6. Random随机淘汰
不建议使用。随机淘汰数据。