380 lines
10 KiB
C++
380 lines
10 KiB
C++
#pragma once
|
||
|
||
#include <cmath>
|
||
#include <memory>
|
||
#include <mutex>
|
||
#include <thread>
|
||
#include <unordered_map>
|
||
#include <vector>
|
||
|
||
#include "KICachePolicy.h"
|
||
|
||
namespace KamaCache
|
||
{
|
||
|
||
template<typename Key, typename Value> class KLfuCache;
|
||
|
||
template<typename Key, typename Value>
|
||
class FreqList
|
||
{
|
||
private:
|
||
struct Node
|
||
{
|
||
int freq; // 访问频次
|
||
Key key;
|
||
Value value;
|
||
std::weak_ptr<Node> pre; // 上一结点改为weak_ptr打破循环引用
|
||
std::shared_ptr<Node> next;
|
||
|
||
Node()
|
||
: freq(1), next(nullptr) {}
|
||
Node(Key key, Value value)
|
||
: freq(1), key(key), value(value), next(nullptr) {}
|
||
};
|
||
|
||
using NodePtr = std::shared_ptr<Node>;
|
||
int freq_; // 访问频率
|
||
NodePtr head_; // 假头结点
|
||
NodePtr tail_; // 假尾结点
|
||
|
||
public:
|
||
explicit FreqList(int n)
|
||
: freq_(n)
|
||
{
|
||
head_ = std::make_shared<Node>();
|
||
tail_ = std::make_shared<Node>();
|
||
head_->next = tail_;
|
||
tail_->pre = head_;
|
||
}
|
||
|
||
bool isEmpty() const
|
||
{
|
||
return head_->next == tail_;
|
||
}
|
||
|
||
// 提那家结点管理方法
|
||
void addNode(NodePtr node)
|
||
{
|
||
if (!node || !head_ || !tail_)
|
||
return;
|
||
|
||
node->pre = tail_->pre;
|
||
node->next = tail_;
|
||
tail_->pre.lock()->next = node; // 使用lock()获取shared_ptr
|
||
tail_->pre = node;
|
||
}
|
||
|
||
void removeNode(NodePtr node)
|
||
{
|
||
if (!node || !head_ || !tail_)
|
||
return;
|
||
if (node->pre.expired() || !node->next)
|
||
return;
|
||
|
||
auto pre = node->pre.lock(); // 使用lock()获取shared_ptr
|
||
pre->next = node->next;
|
||
node->next->pre = pre;
|
||
node->next = nullptr; // 确保显式置空next指针,彻底断开节点与链表的连接
|
||
}
|
||
|
||
NodePtr getFirstNode() const { return head_->next; }
|
||
|
||
friend class KLfuCache<Key, Value>;
|
||
};
|
||
|
||
template <typename Key, typename Value>
|
||
class KLfuCache : public KICachePolicy<Key, Value>
|
||
{
|
||
public:
|
||
using Node = typename FreqList<Key, Value>::Node;
|
||
using NodePtr = std::shared_ptr<Node>;
|
||
using NodeMap = std::unordered_map<Key, NodePtr>;
|
||
|
||
KLfuCache(int capacity, int maxAverageNum = 1000000)
|
||
: capacity_(capacity), minFreq_(INT8_MAX), maxAverageNum_(maxAverageNum),
|
||
curAverageNum_(0), curTotalNum_(0)
|
||
{}
|
||
|
||
~KLfuCache() override = default;
|
||
|
||
void put(Key key, Value value) override
|
||
{
|
||
if (capacity_ == 0)
|
||
return;
|
||
|
||
std::lock_guard<std::mutex> lock(mutex_);
|
||
auto it = nodeMap_.find(key);
|
||
if (it != nodeMap_.end())
|
||
{
|
||
// 重置其value值
|
||
it->second->value = value;
|
||
// 找到了直接调整就好了,不用再去get中再找一遍,但其实影响不大
|
||
getInternal(it->second, value);
|
||
return;
|
||
}
|
||
|
||
putInternal(key, value);
|
||
}
|
||
|
||
// value值为传出参数
|
||
bool get(Key key, Value& value) override
|
||
{
|
||
std::lock_guard<std::mutex> lock(mutex_);
|
||
auto it = nodeMap_.find(key);
|
||
if (it != nodeMap_.end())
|
||
{
|
||
getInternal(it->second, value);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
Value get(Key key) override
|
||
{
|
||
Value value;
|
||
get(key, value);
|
||
return value;
|
||
}
|
||
|
||
// 清空缓存,回收资源
|
||
void purge()
|
||
{
|
||
nodeMap_.clear();
|
||
freqToFreqList_.clear();
|
||
}
|
||
|
||
private:
|
||
void putInternal(Key key, Value value); // 添加缓存
|
||
void getInternal(NodePtr node, Value& value); // 获取缓存
|
||
|
||
void kickOut(); // 移除缓存中的过期数据
|
||
|
||
void removeFromFreqList(NodePtr node); // 从频率列表中移除节点
|
||
void addToFreqList(NodePtr node); // 添加到频率列表
|
||
|
||
void addFreqNum(); // 增加平均访问等频率
|
||
void decreaseFreqNum(int num); // 减少平均访问等频率
|
||
void handleOverMaxAverageNum(); // 处理当前平均访问频率超过上限的情况
|
||
void updateMinFreq();
|
||
|
||
private:
|
||
int capacity_; // 缓存容量
|
||
int minFreq_; // 最小访问频次(用于找到最小访问频次结点)
|
||
int maxAverageNum_; // 最大平均访问频次
|
||
int curAverageNum_; // 当前平均访问频次
|
||
int curTotalNum_; // 当前访问所有缓存次数总数
|
||
std::mutex mutex_; // 互斥锁
|
||
NodeMap nodeMap_; // key 到 缓存节点的映射
|
||
std::unordered_map<int, FreqList<Key, Value>*> freqToFreqList_;// 访问频次到该频次链表的映射
|
||
};
|
||
|
||
template<typename Key, typename Value>
|
||
void KLfuCache<Key, Value>::getInternal(NodePtr node, Value& value)
|
||
{
|
||
// 找到之后需要将其从低访问频次的链表中删除,并且添加到+1的访问频次链表中,
|
||
// 访问频次+1, 然后把value值返回
|
||
value = node->value;
|
||
// 从原有访问频次的链表中删除节点
|
||
removeFromFreqList(node);
|
||
node->freq++;
|
||
addToFreqList(node);
|
||
// 如果当前node的访问频次如果等于minFreq+1,并且其前驱链表为空,则说明
|
||
// freqToFreqList_[node->freq - 1]链表因node的迁移已经空了,需要更新最小访问频次
|
||
if (node->freq - 1 == minFreq_ && freqToFreqList_[node->freq - 1]->isEmpty())
|
||
minFreq_++;
|
||
|
||
// 总访问频次和当前平均访问频次都随之增加
|
||
addFreqNum();
|
||
}
|
||
|
||
template<typename Key, typename Value>
|
||
void KLfuCache<Key, Value>::putInternal(Key key, Value value)
|
||
{
|
||
// 如果不在缓存中,则需要判断缓存是否已满
|
||
if (nodeMap_.size() == capacity_)
|
||
{
|
||
// 缓存已满,删除最不常访问的结点,更新当前平均访问频次和总访问频次
|
||
kickOut();
|
||
}
|
||
|
||
// 创建新结点,将新结点添加进入,更新最小访问频次
|
||
NodePtr node = std::make_shared<Node>(key, value);
|
||
nodeMap_[key] = node;
|
||
addToFreqList(node);
|
||
addFreqNum();
|
||
minFreq_ = std::min(minFreq_, 1);
|
||
}
|
||
|
||
template<typename Key, typename Value>
|
||
void KLfuCache<Key, Value>::kickOut()
|
||
{
|
||
NodePtr node = freqToFreqList_[minFreq_]->getFirstNode();
|
||
removeFromFreqList(node);
|
||
nodeMap_.erase(node->key);
|
||
decreaseFreqNum(node->freq);
|
||
}
|
||
|
||
template<typename Key, typename Value>
|
||
void KLfuCache<Key, Value>::removeFromFreqList(NodePtr node)
|
||
{
|
||
// 检查结点是否为空
|
||
if (!node)
|
||
return;
|
||
|
||
auto freq = node->freq;
|
||
freqToFreqList_[freq]->removeNode(node);
|
||
}
|
||
|
||
template<typename Key, typename Value>
|
||
void KLfuCache<Key, Value>::addToFreqList(NodePtr node)
|
||
{
|
||
// 检查结点是否为空
|
||
if (!node)
|
||
return;
|
||
|
||
// 添加进入相应的频次链表前需要判断该频次链表是否存在
|
||
auto freq = node->freq;
|
||
if (freqToFreqList_.find(node->freq) == freqToFreqList_.end())
|
||
{
|
||
// 不存在则创建
|
||
freqToFreqList_[node->freq] = new FreqList<Key, Value>(node->freq);
|
||
}
|
||
|
||
freqToFreqList_[freq]->addNode(node);
|
||
}
|
||
|
||
template<typename Key, typename Value>
|
||
void KLfuCache<Key, Value>::addFreqNum()
|
||
{
|
||
curTotalNum_++;
|
||
if (nodeMap_.empty())
|
||
curAverageNum_ = 0;
|
||
else
|
||
curAverageNum_ = curTotalNum_ / nodeMap_.size();
|
||
|
||
if (curAverageNum_ > maxAverageNum_)
|
||
{
|
||
handleOverMaxAverageNum();
|
||
}
|
||
}
|
||
|
||
template<typename Key, typename Value>
|
||
void KLfuCache<Key, Value>::decreaseFreqNum(int num)
|
||
{
|
||
// 减少平均访问频次和总访问频次
|
||
curTotalNum_ -= num;
|
||
if (nodeMap_.empty())
|
||
curAverageNum_ = 0;
|
||
else
|
||
curAverageNum_ = curTotalNum_ / nodeMap_.size();
|
||
}
|
||
|
||
template<typename Key, typename Value>
|
||
void KLfuCache<Key, Value>::handleOverMaxAverageNum()
|
||
{
|
||
if (nodeMap_.empty())
|
||
return;
|
||
|
||
// 当前平均访问频次已经超过了最大平均访问频次,所有结点的访问频次- (maxAverageNum_ / 2)
|
||
for (auto it = nodeMap_.begin(); it != nodeMap_.end(); ++it)
|
||
{
|
||
// 检查结点是否为空
|
||
if (!it->second)
|
||
continue;
|
||
|
||
NodePtr node = it->second;
|
||
|
||
// 先从当前频率列表中移除
|
||
removeFromFreqList(node);
|
||
|
||
// 减少频率
|
||
node->freq -= maxAverageNum_ / 2;
|
||
if (node->freq < 1) node->freq = 1;
|
||
|
||
// 添加到新的频率列表
|
||
addToFreqList(node);
|
||
}
|
||
|
||
// 更新最小频率
|
||
updateMinFreq();
|
||
}
|
||
|
||
template<typename Key, typename Value>
|
||
void KLfuCache<Key, Value>::updateMinFreq()
|
||
{
|
||
minFreq_ = INT8_MAX;
|
||
for (const auto& pair : freqToFreqList_)
|
||
{
|
||
if (pair.second && !pair.second->isEmpty())
|
||
{
|
||
minFreq_ = std::min(minFreq_, pair.first);
|
||
}
|
||
}
|
||
if (minFreq_ == INT8_MAX)
|
||
minFreq_ = 1;
|
||
}
|
||
|
||
// 并没有牺牲空间换时间,他是把原有缓存大小进行了分片。
|
||
template<typename Key, typename Value>
|
||
class KHashLfuCache
|
||
{
|
||
public:
|
||
KHashLfuCache(size_t capacity, int sliceNum, int maxAverageNum = 10)
|
||
: sliceNum_(sliceNum > 0 ? sliceNum : std::thread::hardware_concurrency())
|
||
, capacity_(capacity)
|
||
{
|
||
size_t sliceSize = std::ceil(capacity_ / static_cast<double>(sliceNum_)); // 每个lfu分片的容量
|
||
for (int i = 0; i < sliceNum_; ++i)
|
||
{
|
||
lfuSliceCaches_.emplace_back(new KLfuCache<Key, Value>(sliceSize, maxAverageNum));
|
||
}
|
||
}
|
||
|
||
void put(Key key, Value value)
|
||
{
|
||
// 根据key找出对应的lfu分片
|
||
size_t sliceIndex = Hash(key) % sliceNum_;
|
||
lfuSliceCaches_[sliceIndex]->put(key, value);
|
||
}
|
||
|
||
bool get(Key key, Value& value)
|
||
{
|
||
// 根据key找出对应的lfu分片
|
||
size_t sliceIndex = Hash(key) % sliceNum_;
|
||
return lfuSliceCaches_[sliceIndex]->get(key, value);
|
||
}
|
||
|
||
Value get(Key key)
|
||
{
|
||
Value value;
|
||
get(key, value);
|
||
return value;
|
||
}
|
||
|
||
// 清除缓存
|
||
void purge()
|
||
{
|
||
for (auto& lfuSliceCache : lfuSliceCaches_)
|
||
{
|
||
lfuSliceCache->purge();
|
||
}
|
||
}
|
||
|
||
private:
|
||
// 将key计算成对应哈希值
|
||
size_t Hash(Key key)
|
||
{
|
||
std::hash<Key> hashFunc;
|
||
return hashFunc(key);
|
||
}
|
||
|
||
private:
|
||
size_t capacity_; // 缓存总容量
|
||
int sliceNum_; // 缓存分片数量
|
||
std::vector<std::unique_ptr<KLfuCache<Key, Value>>> lfuSliceCaches_; // 缓存lfu分片容器
|
||
};
|
||
|
||
} // namespace KamaCache
|
||
|