LRU原理
LRU(Least Recently Used,最近最少使用)是一种Cache替换算法。LRU Cache的替换原则就是将最近最少使用的内容替换掉。其实,LRU译成最久未使用会更形象,因为该算法每次替换掉的就是一段时间内最久没有使用过的内容。
Cache
什么是Cache?
狭义上讲,Cache是位于CPU和主存之间的高速缓存(快速RAM),广义上讲Cache指位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构。除了CPU与主存之间有Cache,内存与硬盘之间也有Cache,乃至在硬盘与网络之间也有某种意义上的Cache──称为Internet临时文件夹或网络内容缓存等。
-
内存缓存
CPU与主内存之间的缓存。 -
磁盘缓存
磁盘缓存(Disk Buffer)或磁盘快取(Disk Cache)实际上是将下载到的数据先保存于系统为软件分配的内存空间中(这个内存空间被称之为“内存池”),当保存到内存池中的数据达到一个程度时,便将数据保存到硬盘中。这样可以减少实际的磁盘操作,有效的保护磁盘免于重复的读写操作而导致的损坏。磁盘缓存是为了减少CPU透过I/O读取磁盘机的次数,提升磁盘I/O的效率,用一块内存来储存存取较频繁的磁盘内容。相同的技巧可用在写入动作,我们先将欲写入的内容放入内存中,等到系统有其它空闲的时间,再将这块内存的资料写入磁盘中。
-
硬盘与网络之间缓存
在硬盘与网络之间也有某种意义上的Cache──称为Internet临时文件夹或网络内容缓存等
LRU实现
LRU算法描述
LRU 的功能:首先要接收一个 capacity 参数作为缓存的最大容量,然后实现两个 API,一个是 put(key, val) 方法存入键值对,另一个是 get(key) 方法获取 key 对应的 val,如果 key 不存在则返回 -1。同时要求get和put的时间复杂度都是O(1).
/* 缓存容量为 2 */
LRUCache cache = new LRUCache(2);
// 你可以把 cache 理解成一个队列
// 假设左边是队头,右边是队尾
// 最近使用的排在队头,久未使用的排在队尾
// 圆括号表示键值对 (key, val)
cache.put(1, 1);
// cache = [(1, 1)]
cache.put(2, 2);
// cache = [(2, 2), (1, 1)]
cache.get(1); // 返回 1
// cache = [(1, 1), (2, 2)]
// 解释:因为最近访问了键 1,所以提前至队头
// 返回键 1 对应的值 1
cache.put(3, 3);
// cache = [(3, 3), (1, 1)]
// 解释:缓存容量已满,需要删除内容空出位置
// 优先删除久未使用的数据,也就是队尾的数据
// 然后把新的数据插入队头
cache.get(2); // 返回 -1 (未找到)
// cache = [(3, 3), (1, 1)]
// 解释:cache 中不存在键为 2 的数据
cache.put(1, 4);
// cache = [(1, 4), (3, 3)]
// 解释:键 1 已存在,把原始值 1 覆盖为 4
// 不要忘了也要将键值对提前到队头
LRU的实现
LRU的实现核心是哈希表+双向链表,哈希表建立键key和list迭代的映射,这样每次根据key即可找到每个value在list中的位置iterator,哈希表的定义为 <key,list<pair<int,int>>::iterator>, list中存放的是<key,value>,因此定义list为<pair<int,int>>.
代码
.
// An highlighted block
//get 得到对应的value,并且移到队列首。
//put 不存在:队列首加入,此时根据容量可能会挤掉尾元素。存在:移动到队列首
写法1:
class LRUCache {
public:
list<pair<int,int>> cache; //<key,value>
unordered_map<int,list<pair<int,int>>::iterator> map; //哈希表:key 映射到 (key, value) 在 cache 中的位置
int capacity; //cache的容量
LRUCache(int capacity) {
this->capacity=capacity;
}
int get(int key) {
auto it=map.find(key);
if(it==map.end()) return -1;
pair<int,int> temp=*map[key];
cache.erase(map[key]);
cache.push_front(temp);
map[key]=cache.begin();
return temp.second;
}
void put(int key, int value) {
/* 要先判断 key 是否已经存在 */
auto it=map.find(key);
if(it==map.end()){
/*判断cache是否已经满*/
if(cache.size()==capacity){
// cache 已满,删除尾部的键值对腾位置
// cache 和 map 中的数据都要删除
auto lastPair=cache.back();
auto fisrkey=lastPair.first;
map.erase(fisrkey);
cache.pop_back();
}
// cache 没满,可以直接添加,添加到list的头部
cache.push_front(pair{key,value});
map[key]=cache.begin(); // 更新map
}else{
/* key 存在,更改 value 并换到队头 */
cache.erase(map[key]);
cache.push_front(make_pair(key, value));
map[key] = cache.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);
*/
写法2:
class LRUCache {
public:
LRUCache(int capacity) : capacity(capacity) {}
int get(int key) {
if (pos.find(key) != pos.end()){
put(key, pos[key]->second);
return pos[key]->second;
}
return -1;
}
void put(int key, int value) {
if (pos.find(key) != pos.end()) //存在,直接删除list中元素
recent.erase(pos[key]);
else if (recent.size() >= capacity) {
pos.erase(recent.back().first);
recent.pop_back();
}
recent.push_front({ key, value });
pos[key] = recent.begin(); //map中原来存在key的value值被覆盖
}
private:
int capacity;
list<pair<int, int>> recent;
unordered_map<int, list<pair<int, int>>::iterator> pos; //value存储的是一个迭代器
};