LRU算法

LRU原理

LRU(Least Recently Used,最近最少使用)是一种Cache替换算法。LRU Cache的替换原则就是将最近最少使用的内容替换掉。其实,LRU译成最久未使用会更形象,因为该算法每次替换掉的就是一段时间内最久没有使用过的内容

Cache

什么是Cache?
狭义上讲,Cache是位于CPU和主存之间的高速缓存(快速RAM),广义上讲Cache指位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构。除了CPU与主存之间有Cache,内存与硬盘之间也有Cache,乃至在硬盘与网络之间也有某种意义上的Cache──称为Internet临时文件夹或网络内容缓存等。

  1. 内存缓存
    CPU与主内存之间的缓存。

  2. 磁盘缓存
    磁盘缓存(Disk Buffer)或磁盘快取(Disk Cache)实际上是将下载到的数据先保存于系统为软件分配的内存空间中(这个内存空间被称之为“内存池”),当保存到内存池中的数据达到一个程度时,便将数据保存到硬盘中。这样可以减少实际的磁盘操作,有效的保护磁盘免于重复的读写操作而导致的损坏。磁盘缓存是为了减少CPU透过I/O读取磁盘机的次数,提升磁盘I/O的效率,用一块内存来储存存取较频繁的磁盘内容。

    相同的技巧可用在写入动作,我们先将欲写入的内容放入内存中,等到系统有其它空闲的时间,再将这块内存的资料写入磁盘中。

  3. 硬盘与网络之间缓存
    在硬盘与网络之间也有某种意义上的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>>.

LRU数据结构
代码.

// 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存储的是一个迭代器
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值