1. LFU 原理简述
LFU(Least Frequently Used)缓存的核心思想是:每次访问缓存元素,记录其访问次数;淘汰访问次数最少的元素。
与 LRU 不同,LFU关注的是访问频率而非最近访问时间。
实现 LFU 通常需要:
- 哈希表用于 O(1) 查找元素。
- 频率表(如 TreeMap 或链表+哈希表)用于维护各频率下的元素集合。
- 当缓存满时,删除访问次数最少且最早插入的元素。
2. Java LFU Demo
下面是一个简单的 Java LFU 缓存实现(核心逻辑,适合学习):
import java.util.*;
class LFUCache<K, V> {
private final int capacity;
private int minFreq = 0;
private final Map<K, V> valueMap; // key -> value
private final Map<K, Integer> freqMap; // key -> freq
private final Map<Integer, LinkedHashSet<K>> freqListMap; // freq -> keys
public LFUCache(int capacity) {
this.capacity = capacity;
this.valueMap = new HashMap<>();
this.freqMap = new HashMap<>();
this.freqListMap = new HashMap<>();
}
public V get(K key) {
if (!valueMap.containsKey(key)) return null;
int freq = freqMap.get(key);
freqMap.put(key, freq + 1);
freqListMap.get(freq).remove(key);
freqListMap.computeIfAbsent(freq + 1, k -> new LinkedHashSet<>()).add(key);
if (freqListMap.get(freq).isEmpty()) {
freqListMap.remove(freq);
if (freq == minFreq) minFreq++;
}
return valueMap.get(key);
}
public void put(K key, V value) {
if (capacity <= 0) return;
if (valueMap.containsKey(key)) {
valueMap.put(key, value);
get(key); // 更新频率
return;
}
if (valueMap.size() >= capacity) {
// 淘汰 minFreq 下最早插入的 key
LinkedHashSet<K> keys = freqListMap.get(minFreq);
K evictKey = keys.iterator().next();
keys.remove(evictKey);
if (keys.isEmpty()) freqListMap.remove(minFreq);
valueMap.remove(evictKey);
freqMap.remove(evictKey);
}
// 插入新 key
valueMap.put(key, value);
freqMap.put(key, 1);
freqListMap.computeIfAbsent(1, k -> new LinkedHashSet<>()).add(key);
minFreq = 1;
}
}
3. 使用示例
public class Main {
public static void main(String[] args) {
LFUCache<Integer, String> cache = new LFUCache<>(2);
cache.put(1, "A");
cache.put(2, "B");
System.out.println(cache.get(1)); // 输出 A
cache.put(3, "C"); // 淘汰 key=2(频率最低)
System.out.println(cache.get(2)); // 输出 null
System.out.println(cache.get(3)); // 输出 C
cache.put(4, "D"); // 淘汰 key=1 或 key=3(根据频率)
System.out.println(cache.get(1)); // 输出 null 或 A(取决于前面的访问)
System.out.println(cache.get(3)); // 输出 null 或 C
System.out.println(cache.get(4)); // 输出 D
}
}
4. 原理总结
- 每次访问元素,访问频率 +1,并将元素移到更高频率的集合。
- 缓存满时,淘汰频率最低且最早插入的元素。
- 哈希表和链表结合,保证查找、插入、淘汰都是 O(1) 时间复杂度(核心思想)。
LFU 更适合场景:热点数据长期稳定,频率统计能更好地保留高频访问数据。**