菜鸟笔记
提升您的技术认知

面试题

实现一个LRU Cache 算法LRU Cache C++三种解法java实现LRU算法及编码实现LRU策略缓存LRU算法常见缓存算法和LRU的c++实现设计循环双端队列(deque)LRU 缓存结构 (c++ 哈希双链表实现)LRU缓存机制删除单链表中的指定节点Linux 内核经典面试题拼多多社招面经:Redis是重点,讲一讲redis的内存模型线程、进程、协程的区别C++经典面试题面试官:我们只想要这样的C++工程师Linux C/C++ 学习路线链表操作汇总C++11的智能指针面试题浏览器中输入url后发生的事情常用的限流算法HTTP协议和HTTPS协议面试题网络编程面试题目总结c++后台面试题目如何实现LRU算法?如何寻找无序数组中的第K大元素?布隆过滤器 - 如何在100个亿URL中快速判断某URL是否存在?如何实现大整数相加?C++面试题及基本知识点总结C++给定出栈序列判定是否合法消息队列面试题要点redis缓存击穿,失效以及热点key解决方案网页在浏览器上的渲染过程几种限流算法lru算法例题C/C++常见面试知识点总结附面试真题----20210529更新引入MQ消息队列的作用及其优缺点MySQL面试篇社招三年后端面试题60道测开面试题,背完直接涨工资二叉树的层序遍历(两种方法实现)Bitmap 海量数据处理字符串倒序输出的五种方法C语言 输入10个数,统计出并输出正数、负数和0的个数字节三面:如何设计一个高并发系统架构,网络 面试36问,DDos攻击原理C++线程池使用 C++11 编写可复用多线程任务池LruCache结合Any容器类型实现任意类型缓存c++实现LFU算法

c++实现LFU算法

阅读 : 16

LFU (Least Frequently Used),是一种缓存算法,它对每一个数据块都有一个访问次数的记录,也就是一个数据块的访问次数越大,在将来越可能访问。

1.每个节点(数据块)都有一个访问次数,新插入的节点访问次数为1
2.相同访问次数的依据时间排序,后插入的在前面
3.当需要淘汰数据时,会从尾部,也就是访问次数从小到大,开始淘汰

这里是使用c++实现的LFU缓存:主要利用了一个multiset,unorder_map分别来根据访问次数排序和保存数据。

这里是需要使用的节点结构

#include <string>
#include <unordered_map>
#include <set>
using namespace std;

template<class KEY>
struct CacheKey
{
	KEY		key;
	int		count;
	CacheKey(KEY k) :key(k), count(1) {}
	void change(int x) {
		count += x;
	}
    int getcount() {
		return count;
	}
};

template<class KEY,class VAL>
struct CacheNode
{
	CacheKey<KEY>* cacheKey;
	VAL val;
	CacheNode(CacheKey<KEY>* ck, VAL v) :cacheKey(ck), val(v) {}
	CacheNode(KEY k, VAL v):val(v) {
		cacheKey = new CacheKey<KEY>(k);
	}
};

template<class KEY>
struct CMP     //仿函数,提供CacheKey的比较方式
{
	bool operator()(CacheKey<KEY> const* x, CacheKey<KEY> const* y) {
		return x->count < y->count;
	}
};

c++ LFU

/*
LFU (least Frequently Used)
根据数据的历史访问次数来决定淘汰数据,也就是当前时刻访问次数大的数据,
在下一时刻也可能被访问到。
1.每个节点(数据块)都有一个访问次数
2.相同访问次数的依据时间排序,后插入的在前面
3.当需要淘汰数据时,会从尾部,也就是访问次数从小到大,开始淘汰
*/
template<class KEY, class VAL >
class Lfu
{
public:
	Lfu(size_t size) :cacheSize(size){

	}
	~Lfu() {
		auto ite = cacheMap.begin();
		while (ite != cacheMap.end()) {
			delete ite->second->cacheKey;
			delete ite->second;
			++ite;
		}
		cacheMap.clear();
		cacheSet.clear();
	}
public:
	void		put(KEY key, VAL val) {
		this->check_cache_size();
		auto node = new CacheNode<KEY, VAL>(key, val);
		cacheMap[key] = node;
		cacheSet.insert(node->cacheKey);
	}

	bool		get(KEY key, VAL* val) {
		if (cacheMap.count(key)) {
			auto node = this->removeFromSet(key);
			if(val)
				*val = node->val;
			node->cacheKey->change(1);
			cacheSet.insert(node->cacheKey);
			return true;
		}
		return false;
	}

	VAL			get(KEY key) {
		VAL v;
		this->get(key, &v);
		return v;
	}

	void        remove(KEY key) {
        if(cacheMap.count(key)){
            auto node = cacheMap[key];
            this->removeFromSet(node->key);
            delete node->key;
            delete node;
            cacheMap.erase(key);
        }
	}

	bool		exist(KEY key) {
		if (cacheMap.count(key))
			return true;
		return false;
	}

	void		print_test() {
		auto ite = cacheSet.begin();
		while (ite != cacheSet.end()) {
			cout << "count: " << (*ite)->count << " key: " << (*ite)->key << endl;
			++ite;
		}
		cout << "======================================" << endl;
	}
private:
	//将节点从set中删除,返回删除的node
	void     removeFromSet(CacheKey<KEY>* key) {	
		auto ite = cacheSet.find(key);
        while(ite != cacheSet.end()){
            if(key == cacheMap[(*ite)->key]->key){
                cacheSet.erase(ite);
                break;
            }
            auto itex = ite;
            ++ite;
            if(ite != cacheSet.end() && (*ite)->getcount() != (*itex)->getcount()){
                break;
            }
        }
	}
	//检查缓存数量是否超出了大小限制
	void	check_cache_size() {	
		while (cacheMap.size() >= cacheSize) {
			auto ite = cacheSet.begin();
			cacheMap.erase((*ite)->key);
			cacheSet.erase(ite);
		}
	}
private:
	multiset<CacheKey<KEY>*,CMP<KEY>>            cacheSet;	//利用红黑树根据访问次数排序
	unordered_map<KEY, CacheNode<KEY, VAL>*>     cacheMap;	//缓存MAP
	size_t                                       cacheSize;	//缓存空间大小
};

LFU-Aging LFU的改进版

由于LFU的淘汰策略是淘汰访问次数最小的数据块,但是新插入的数据块的访问次数为1,这样就会产生缓存污染,使得新数据块被淘汰。所以在LFU算法之上,引入访问次数平均值概念,当平均值大于最大平均值限制时,将所有节点的访问次数减去最大平均值限制的一半或一个固定值。

c++ LFU-Aging

/*
LFU-Aging 对于LFU的改进版
LFU对于新数据不友好,因为新插入的数据访问次数count=1,放在尾部
如果需要淘汰就会将新数据淘汰。
所以LFU-Aging加入了平均访问次数的概念
如果节点的平均访问次数大于某个固定值x时,则将所有节点的count值减去x/2
这样可解决“缓存污染”
*/
template<class KEY, class VAL >
class LfuAging
{
public:
	LfuAging(size_t size,int maxAverage = 10) 
		:cacheSize(size), countMaxAverage(maxAverage), countCurAverage(1), countAll(0){

	}
	~LfuAging() {
		auto ite = cacheMap.begin();
		while (ite != cacheMap.end()) {
			delete ite->second->cacheKey;
			delete ite->second;
			++ite;
		}
		cacheMap.clear();
		cacheSet.clear();
	}
public:
	void		put(KEY key, VAL val) {
		this->check_cache_size();
		auto node = new CacheNode<KEY, VAL>(key, val);
		cacheMap[key] = node;
		cacheSet.insert(node->cacheKey);
        this->average(1);	
	}

	bool		get(KEY key, VAL* val) {
		if (cacheMap.count(key)) {
			auto node = this->removeFromSet(key);
			if (val)
				* val = node->val;
			node->cacheKey->change(1);
			cacheSet.insert(node->cacheKey);
			this->average(1);
			return true;
		}
		return false;
	}

	VAL			get(KEY key) {
		VAL v;
		this->get(key, &v);
		return v;
	}

	void        remove(KEY key) {
		auto node = this->removeFromSet(key);
		if (node) {
			cacheMap.erase(key);
			this->average(-node->cacheKey->getcount());
			delete node->cacheKey;
			delete node;
		}
	}

	bool		exist(KEY key) {
		if (cacheMap.count(key))
			return true;
		return false;
	}

	void		print_test() {
		auto ite = cacheSet.begin();
		while (ite != cacheSet.end()) {
			cout << "count: " << (*ite)->count << " key: " << (*ite)->key << endl;
			++ite;
		}
		cout << "======================================" << endl;
	}
private:
	CacheNode<KEY, VAL>* removeFromSet(KEY key) {
		if (cacheMap.count(key)) {
			auto node = cacheMap[key];
			auto ite = cacheSet.begin();
			while (ite != cacheSet.end()) {
				if ((*ite) == node->cacheKey) {
					cacheSet.erase(ite);
					break;
				}
				++ite;
			}
			return node;
		}
		return nullptr;
	}

	void	check_cache_size() {
		while (cacheMap.size() >= cacheSize) {
			auto ite = cacheSet.begin();
			cacheMap.erase((*ite)->key);
			cacheSet.erase(ite);
		}
	}

	void		average(int change) {
		countAll += change;
		int av = countAll / cacheMap.size();
		if (av >= countMaxAverage) {
			auto ite = cacheSet.begin();
			while (ite != cacheSet.end()) {
				(*ite)->change(-(countMaxAverage / 2));
				++ite;
			}
			countAll = countAll - ((countMaxAverage / 2) * cacheMap.size());
		}
	}
private:
	multiset<CacheKey<KEY>*, CMP<KEY>>            cacheSet;
	unordered_map<KEY, CacheNode<KEY, VAL>*>      cacheMap;
	size_t                                        cacheSize;
	int                                           countMaxAverage;
	int                                           countCurAverage;
	int                                           countAll;
};

LFU与LRU对比

LFU算法的命中率会更高一些,且能解决一些周期性的访问带来命中率下降的情况。