淘汰策略之LFU

37 阅读2分钟

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. 每次访问元素,访问频率 +1,并将元素移到更高频率的集合。
  2. 缓存满时,淘汰频率最低且最早插入的元素。
  3. 哈希表和链表结合,保证查找、插入、淘汰都是 O(1) 时间复杂度(核心思想)。

LFU 更适合场景:热点数据长期稳定,频率统计能更好地保留高频访问数据。**