高效缓存系统:C++实现LFU策略

技术文章大纲:C++实现具备LFU的缓存系统

引言

简要介绍LFU(Least Frequently Used)缓存淘汰策略的概念及其应用场景。说明实现LFU缓存系统的必要性,例如在数据库、Web服务器等高频访问系统中优化性能。

LFU缓存的核心原理

解释LFU策略的基本逻辑:根据数据项的访问频率决定淘汰顺序,访问频率最低的数据优先被移除。对比LRU(Least Recently Used)策略,突出LFU在特定场景下的优势。

数据结构设计

分析实现LFU缓存所需的关键数据结构:

  • 哈希表(unordered_map):存储键值对,实现O(1)时间复杂度的查找。
  • 红黑树(map)维持频率桶,相同频率的键用链表存储
  • 一个迭代器类型(unordered_map<::iterator>),用于指向节点在链表中的位置
//核心数据结构
std::unordered_map<Key, std::pair<Value, int>>KeyValueFreq;//哈希表,存储键值和访问次数
std::map<int, std::list<Key>>freqKeys;//红黑树维持频率桶,相同频率的键用链表存储
std::unordered_map<Key, typename std::list<Key>::iterator>KeyIterator;//指向节点在链表中的位置
int min_freq;//记录最小频率

具体实现步骤

  1. 定义节点结构体
    template<typename Key,typename Value>可以灵活的适用于不同参数。

  2. 核心类设计(LFUCache类)

    • 成员变量:容量(capacity)、当前最小频率(min_freq)、键到节点的哈希表(keyValueFreq)、频率到节点链表的哈希表(freqKeys)。
    • 方法:get(获取数据)、put(插入/更新数据)、私有方法updata_min_frequency_after(更新min_freq的值)increase_frequency(增加键的频率)remove_min_frequence(移除最小频率)。
      void updata_min_frequency_after(int r) {
      	if (r != min_freq) {
      		return;
      	}
      	if (freqKeys.empty()) {
      		min_freq = 1;//重置
      	}
      	else {
      		auto f = freqKeys.begin();
      		min_freq = f->first;
      	}
      }
      //内部辅助方法 增加键的频率
      void increase_frequency(const Key& K) {
      	int oldFreq = KeyValueFreq[K].second;//获取键的旧频率
      	auto& oldKey = freqKeys[oldFreq];//获取键在频率桶的位置
      	oldKey.erase(KeyIterator[K]);//移除它
      	if (oldKey.empty()) {//如果旧桶空了
      		freqKeys.erase(oldFreq);//清除桶
      		if (oldFreq == min_freq) {//如果恰好是最小频率,则更新最小频率
      			updata_min_frequency_after(oldFreq);
      		}
      	}
      	int newFreq = oldFreq + 1;
      	KeyValueFreq[K].second = newFreq;
      	freqKeys[newFreq].push_front(K);//将新频率键插入链表前
      	KeyIterator[K] = freqKeys[newFreq].begin();//更新指示器位置
      }
      //内部辅助方法 移除最小频率
      void remove_min_frequence() {
      	//找最小频率桶的最后一个键(LFU+LRU)
      	auto minfreqkeys = freqKeys[min_freq];//找最小频率桶
      	Key removed = minfreqkeys.back();//找最小频率桶的最后一个键
      	//从所有数据结构中开始删除
      	minfreqkeys.pop_back();
      	KeyValueFreq.erase(removed);
      	KeyIterator.erase(removed);
      }
  3. 关键操作实现

    • get操作:若键存在,更新节点频率并返回值;否则返回-1。
    • put操作:若键存在则更新值并调频;若不存在且缓存已满,淘汰最小频率的尾节点,再插入新节点。
    • printDubeg操作是打印函数。
    • 每个操作都有加锁处理
    • 需要注意的是,这里是方法重写,因为get操作和put还用于LRU和ARC的实现。
void put(const Key& K, const Value& V) override {   //重写PUT函数
	std::lock_guard<std::mutex>lock(mutex_);
	printDebug("PUT", K);
	auto it = KeyValueFreq.find(K);
	if (it != KeyValueFreq.end()) {//判断是否有K
		it->second.first = V;
		increase_frequency(K);
	}
	else {//如果没有就需要插入,还需判断储存空间
		if (KeyValueFreq.size() >= capacity) {
			remove_min_frequence();
		}
		KeyValueFreq[K] = { V,1 };//在哈希表中插入
		freqKeys[1].push_front(K);//在频率桶中更新
		KeyIterator[K] = freqKeys[1].begin();//更新KeyIterator
		min_freq = 1;
	}
}
std::optional<Value>get(const Key& K)override {//重写get方法
	std::lock_guard<std::mutex>lock(mutex_);
	auto it = KeyValueFreq.find(K);
	if (it != KeyValueFreq.end()) {
		increase_frequency(K);
		printDebug("Get hit", K);
		return it->second.first;
	}
	printDebug("Get miss", K);
	return std::nullopt;
}

复杂度分析

  • 时间复杂度:getput操作均为O(1)。
  • 空间复杂度:O(n),n为缓存容量。

优化内容

  • 并发支持:通过加锁(如std::mutex)实现线程安全。
  • 性能测试:对比LFU与LRU在不同访问模式下的命中率。
  • 变种策略:结合LRU的LFU-Aging策略,避免历史高频但近期低频的数据长期占用缓存。
void compareLRUvsLFU() {
	std::cout << "\n=== LRU vs LFU 对比测试 ===" << std::endl;

	std::cout << "\n场景:周期性访问模式" << std::endl;

	// LRU缓存
	CacheManager<int, std::string> lruCache(CacheType::LRU, 2);
	// LFU缓存  
	CacheManager<int, std::string> lfuCache(CacheType::LFU, 2);

	// 访问序列: 1, 2, 1, 2, 3, 1, 2, 3
	int access_sequence[] = { 1, 2, 1, 2, 3, 1, 2, 3 };

	for (int i = 0; i < 8; ++i) {
		int key = access_sequence[i];

		// 检查是否已存在:使用 contains()
		if (!lruCache.contains(key)) {
			lruCache.put(key, "Value" + std::to_string(key));
		}
		else {
			lruCache.get(key); // 更新 LRU 顺序
		}

		if (!lfuCache.contains(key)) {
			lfuCache.put(key, "Value" + std::to_string(key));
		}
		else {
			lfuCache.get(key); // 更新 LFU 频率
		}
	}
}

应用案例

  • LRU:适合访问模式相对稳定,有明显热点数据的场景,例如数据库连接池、Web服务器缓存

  • LFU:适合访问频率差异大,需要长期保护高频数据的场景,例如操作系统页面缓存、电商商品缓存。

总结

     LFU(Least Frequently Used)缓存基于访问频率淘汰数据,高频访问的条目保留更久。适合长期热点数据统计的场景,如内容推荐系统或广告投放平台,能有效减少高频数据的重复计算。访问模式稳定的系统(如新闻门户的热点排行榜)能最大化LFU的价值。其算法通过哈希表+最小堆或双哈希表实现,时间复杂度可优化至O(1)。

    突发流量可能导致低频但新写入的数据被快速淘汰("缓存污染"问题)。例如社交媒体的突发热搜话题可能因初始低频被LFU误删。实现复杂度高于LRU,需维护频率计数器和多层数据结构。

    适合场景:视频点播的热门影片缓存、电商长期畅销商品展示、企业级数据库查询缓存。这类场景中热点数据生命周期较长,访问规律性强。

   完整代码在https://github.com/huan03189/Cache-System-LRU-and-LFU.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值