1、什么是LFU缓存?
LFU(Least Frequently Used)缓存是一种缓存替换算法,用于在有限的缓存空间中管理数据的存储和访问。LFU缓存根据数据项被访问的频率来决定哪些数据项应该被保留在缓存中,以便提高缓存的命中率。
LFU缓存的基本思想是,当缓存空间已满时,优先删除访问频率最低的数据项,以便为更频繁访问的数据项腾出空间。这种策略可以有效地利用缓存空间,保留那些被频繁访问的数据项,提高缓存的效率。
2、设计LFU缓存流程(双哈希表实现)
1. 使用一个哈希表 cache 来存储key和对应的节点,节点包括key、value、频次freq。 2. 使用一个哈希表 freqList 来存储频次和对应的频次列表,频次列表按时间顺序存储节点。 3. 使用一个变量 minFreq 来记录当前最小频次。 4、实现频率更新update方法 - 取出节点对应频次表,从表删除该节点 - 如果该列表是频次最低的列表并且该数据点是最后一个,更新缓存最低频次minFreq+1 - 更新该节点频次freq+1 - 将该节点添加新的频次表 4. 实现 set 方法: - 如果key已存在,更新节点的value,并更新频次。 - 如果key不存在,判断缓存是否已满: - 如果缓存已满,删除最小频次列表第一个节点,并从 cache 中删除对应的记录。 - 创建新节点,并将其插入到频次为1的链表的末尾,更新 cache 和 freqList 。 - 更新 minFreq 为1。 5. 实现 get 方法: - 如果key不存在,返回-1。 - 如果key存在,获取节点,更新频次。 - 返回节点的value。
具体实现
public class LFUCache {
class Node {
int key;
int value;
int freq;//频率
public Node(int key, int value, int freq) {
this.key = key;
this.value = value;
this.freq = freq;
}
}
private int capacity;
private int size;
private int minFreq;//频率最低
private HashMap<Integer, Node> cache;//缓存
private HashMap<Integer, LinkedHashSet<Node>> freqList;//缓存频率表(每个频次都有一个表),因为有很多相同频次的数据
public LFUCache(int capacity) {
this.capacity = capacity;
size = 0;
minFreq = 0;
cache = new HashMap<>();
freqList = new HashMap<>();
}
public int get(int key) {//获取
if (!cache.containsKey(key)) {
return -1;
}
Node node = cache.get(key);
updateNode(node);
return node.value;
}
public void set(int key, int value) {
if (capacity == 0) {
return;
}
//存在则更新
if (cache.containsKey(key)) {
Node node = cache.get(key);
node.value = value;
updateNode(node);
} else {//不存在则新增
if (size == capacity) {//空间占满,删除频率最低数据
LinkedHashSet<Node> minFreqList = freqList.get(minFreq);//获取最小频率列表
Node node = minFreqList.iterator().next();//获取第一个节点
minFreqList.remove(node);//移除节点
cache.remove(node.key);//缓存也移除
size--;
}
//新增节点-加入缓存-加入频率表-更新最小频次和缓存大小
Node newNode = new Node(key, value, 1);
cache.put(key, newNode);
LinkedHashSet<Node> newList = freqList.getOrDefault(1, new LinkedHashSet<>());
newList.add(newNode);
freqList.put(1, newList);
minFreq = 1;
size++;
}
}
private void updateNode(Node node) {//更新数据频率
//取出-移除
LinkedHashSet<Node> oldList = freqList.get(node.freq);//获取该频次的列表
oldList.remove(node);//移除该节点
if (oldList.isEmpty() && node.freq == minFreq) {//如果这数据是该表最后一个数据并且是最低频率
//更新最小频率
minFreq++;
}
//更新-新增
node.freq++;
//获取该频次的列表(没有则新增)
LinkedHashSet<Node> newList = freqList.getOrDefault(node.freq, new LinkedHashSet<>());
newList.add(node);//列表新增节点
freqList.put(node.freq, newList);//放入(列表存在则更新,不存在则新增)
}
}