什么是LRUCache
首先我们来看看什么是cache
cache的核心作用是作为一组缓冲区来降低不同介质之间的速度差异。
那么问题来了,cache满了怎么办?
显然,满了就需要删除掉旧的,替换进去新的内容。
但是该如何替换呢?也就是替换策略是什么样的呢?
目前,最常用的替换策略就是LRU(Least Recently Used),意思是最近最少使用,也就是当cache满了以后,用新的数据替换最近最少使用的数据。
顾名思义,LRUCache就是采用LRU替换策略的cache。
LRUCache的实现
LRUCache的实现,我们以一道leetcode的题目为例
传送门:leetcode链接
cache需要实现的功能主要有查找和插入。
想要实现LRUCache的功能是很简单的,但是,想要实现高效的LRUCache并不简单。
所谓高效,我们定义为,插入和查找的时间复杂度都达到O(1)
LRUCache的结构(核心)
想要查找和插入的时间复杂度为O(1),很显然想到hash表
但是如何实现LRU策略呢?
这里,我们的方法是使用一个list容器
当一个数据被使用之后,立即提到list的头部
这样,list的尾的数据,就是LRU的,即最近最少使用的。
所以,我们的结构真的是下面的样子吗?
class LRUCache
{
private:
unordered_map<int,int> _hash;
list<pair<int,int>> _list;
int _capacity;
};
来,我们思考一下
当我们要修改一个数据的时候,我们是不是要先找到,才能修改
hash表中查找很简单,但是list中查找需要遍历一遍,时间复杂度是O(N),显然,就违背了我们高效的初衷。
那怎么办呢?
LRU没办法实现高效的设计吗?
前人给出了天才般的设计。
class LRUCache {
private:
unordered_map<int,list<pair<int,int>>::iterator> _hash;//通过迭代器可以实现
//链表的O(1)的查找
list<pair<int,int>> _list;//链表的查找是O(N),直接使用链表不行
int _capacity;
};
在原来的设计中,hash和list中都存了value,这显然浪费了呀,凭啥要存两次啊,脸大吗?
当然这样的设计维护起来肯定是要稍微麻烦一点的,一点修改,就需要两个容器同时维护。
LRUCache的查找
有一处细节需要注意
当我们找到了数据后,代表着这条数据已经使用过,就需要将他提到list的头部,同时hash也要对应修改
其余非常简单,直接看代码即可
int get(int key)
{
auto it = _hash.find(key);
if(it != _hash.end())
{
_list.splice(_list.begin(),_list,it->second);
_hash[key] = _list.begin();
return (it->second)->second;
}
else
return -1;
}
LRUCache的插入
如果key已经存在,那就直接更新即可,更新完后,提到list的头部。
如果key不存在,那就直接插入即可,
1. LRUCache满了,尾删,然后头插
2. LRUCache没满,直接头插
更新list的同时要一起更新hash表
void put(int key, int value)
{
auto it = _hash.find(key);
if(it != _hash.end())//找到了,直接更新即可
{
it->second->second = value;
_list.splice(_list.begin(),_list,it->second);
}
else//没找到,要新插入
{
if(_list.size() == _capacity)//把最近不使用的元素删除掉
{
pair<int,int> back = _list.back();
_list.pop_back();
_hash.erase(back.first);
}
_list.push_front({key,value});
_hash[key] = _list.begin();
}
}
完整代码
class LRUCache {
private:
unordered_map<int,list<pair<int,int>>::iterator> _hash;//通过迭代器可以实现
//链表的O(1)的查找
list<pair<int,int>> _list;//链表的查找是O(N),直接使用链表不行
int _capacity;
public:
LRUCache(int capacity) {
_capacity = capacity;
}
int get(int key) {
auto it = _hash.find(key);
if(it != _hash.end())
{
_list.splice(_list.begin(),_list,it->second);
_hash[key] = _list.begin();
return (it->second)->second;
}
else
return -1;
}
void put(int key, int value) {
auto it = _hash.find(key);
if(it != _hash.end())//找到了,直接更新即可
{
it->second->second = value;
_list.splice(_list.begin(),_list,it->second);
}
else//没找到,要新插入
{
if(_list.size() == _capacity)//把最近不使用的元素删除掉
{
pair<int,int> back = _list.back();
_list.pop_back();
_hash.erase(back.first);
}
_list.push_front({key,value});
_hash[key] = _list.begin();
}
}
};
/**
* 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);
*/