LFU 缓存算法详解
一、核心原理
基础规则:
优先淘汰历史访问频率最低的数据(长期统计维度)。
- 每个缓存条目维护两个核心属性:键值对数据 + 访问频率计数器。当缓存容量达到上限时,系统会选择当前所有数据中访问频率最低的条目进行淘汰;若多个数据的频率相同,则进一步淘汰其中最久未被访问的(类似 LRU 的兜底逻辑)。
操作流程:
- 访问数据时:命中缓存后,该数据的访问频率+1,并调整其在频率排序中的位置(确保高频数据优先保留,也就是在双向链表头部)。
- 写入新数据时:若缓存未满,直接插入(初始频率通常为 1 或 0+1);若缓存已满,先淘汰频率最低的数据,再写入新条目(新数据频率初始化为 1);如果对应 key 值存在,那就更新对应 value,最后再调整对应频率。
二、关键特性与实现机制
1. 数据结构设计(高效实现的核心)
LFU 通常通过双哈希表 + 频率双向链表的组合实现 O(1) 时间复杂度的操作:
[图:双哈希表与双向链表结构示意图]
- 存储哈希表(Key-Node):存储键到缓存条目数据集的映射(快速定位数据,方便及时更新频率操作)。
- 频率哈希表:以频率值为键,而 key 类型的双向链表为值,维护该频率下的所有数据节点(通常用双向链表存储,支持快速插入/删除)。
- 最小频率计数器:记录当前缓存中最低的频率值,淘汰时直接定位到该频率链表。
例如:当数据 A 被访问时,其频率从 1→2,需从原频率 1 的链表移除,并插入到频率 2 的链表头部;若新数据 B 写入,初始频率为 1,插入频率 1 链表。
2. 频率动态更新
每次访问(读/写)缓存数据时,触发频率+1 的更新操作:
- 从原频率对应的链表中移除该数据节点;
- 将频率值+1,并插入到新频率对应的链表头部(保证最近访问的数据在链表前端);
- 更新全局最小频率(若原最小频率链表为空,则最小频率+1)。
3. 实现思想及代码测试
我们下面要设计的这个 LFU 算法逻辑和上面的演示图是差不多,只不过我们存储的不是自定义节点的地址,而是这个 Node 对象,当然地址也行,其他都差不多。
对应的节点结构如下:
// 定义对应的节点结构
template<class K, class V>
struct Node {
Node() {}
// 表明对应的默认对象必须存在,hash[key] = Node()
Node(const K& key, const V& value, int access_count)
: key(key), value(value), (access_count) {}
K key;
V value;
access_count;
std::list<K>::iterator it;
};


