LRU Cache
点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃
1.什么是LRU Cache
LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法。 什么是Cache?狭义的Cache指的是位于CPU和主存间的快速RAM, 通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。 广义上的Cache指的是位于速度相差较大的两种硬件之间, 用于协调两者数据传输速度差异的结构。除了CPU与主存之间有Cache, 内存与硬盘之间也有Cache,乃至在硬盘与网络之间也有某种意义上的Cache── 称为Internet临时文件夹或网络内容缓存等。
Cache的容量有限,因此当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容。LRU Cache 的替换原则就是将最近最少使用的内容替换掉。其实,LRU译成最久未使用会更形象, 因为该算法每次替换掉的就是一段时间内最久没有使用过的内容。
2.LRU Cache的实现
下面借助oj题来实现。
题目链接:146. LRU 缓存
LRU Cache最重要的就是两个操作put和get,它要求必须以 O(1) 的平均时间复杂度运行。
还有这道题LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存,超过capacity就满了,实际上可能要按照字节来算。
get就是给一个关键字但是实际上是地址,看这个地址在不在,在的话就是在缓存,然后去缓存在去取这块的数据。
put有两种场景,有可能是新增,有可能是更新,如果关键字在了就是更新,不在就是新增。如果插入的时候导致关键字数量capacity,就把最近最少使用的删除。·
那如何设计出这里的结构呢?
大部分看到put和get涉及查找并且要求O(1),可能会想到用哈希表。unordered_map<int,int> _hashMap,这样查是O(1),更新O(1),新增O(1)。
但是真正要考虑的是LRU,满了删谁。谁是最近最少用的,map能帮查找查是O(1),更新O(1),新增O(1),如何保证LRU?
LRU涉及顺序的问题。那个数据结构可以满足顺序吗?
vector和list都可以。
这里我们用的是list控制LRU,list<pair<int,int>> _LRUlist,如何控制其实很简单,在_LRUlist里面默认认为尾巴的是最近最少使用,只要一个数据被用了就提到队头,如果在插入一个数据满了,就把尾巴上的那个最近最少使用的删除。
但是unordered_map<int,int> 和 list<pair<int,int>> 结合起来并不是所有操作都能做到O(1)。get查map可以做到O(1),put新增是O(1),更新是O(n),因为我们的数据在map存一份在list存一份,新增map是O(1),list头插也是O(1),虽然更新map是O(1),然后要调整_LRUlist中key的位置到头部,更新顺序,但是我们不知道数据在哪,只能遍历去查,这个就是O(n)。只要访问过的数据都要往队头提!
那怎么做呢?
找到key,就要找到key对于存储数据在list中位置。
unordered_map 的 value给一个list的迭代器。指向对于key存储数据在list中位置,这样查找,新增,更新都是O(1) 都先去unordered_map 查key然后在做对应操作。
list还有一个接口splice(接合),可以不用保存值删节点然后重新new插入头,而是直接把节点转移队头。
class LRUCache
{
public:
LRUCache(int capacity)
:_capacity(capacity)
{}
int get(int key)
{
auto ret = _hashMap.find(key);
if (ret != _hashMap.end())
{
//更新key对应值的位置
LtIter it = ret->second;
//1. erase + push_front + 更新迭代器,原迭代器失效
//2. splice接合,转移节点
_LRUlist.splice(_LRUlist.begin(), _LRUlist, it);
return it->second;
}
else
{
return -1;
}
}
void put(int key, int value)
{
//1.新增
//2.更新
auto ret = _hashMap.find(key);
if (ret == _hashMap.end())
{
//满了,先删除LRU的数据
if (_capacity == _hashMap.size())
{
auto back = _LRUlist.back();
_LRUlist.pop_back();
_hashMap.erase(back.first);
}
//插入
_LRUlist.push_front(make_pair(key, value));
_hashMap[key] = _LRUlist.begin();
}
else
{
//更新key对应值的位置
LtIter it = ret->second;
//更新
it->second = value;
_LRUlist.splice(_LRUlist.begin(), _LRUlist, it);
}
}
private:
typedef list<pair<int, int>>::iterator LtIter;
//hash做到查找更新是O(1)
unordered_map<int, LtIter> _hashMap;
//LRU 假设尾部数据就是最近最少使用
list<pair<int, int>> _LRUlist;
size_t _capacity;
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/