Cache的设计和实现 LRU Cache

142 篇文章 20 订阅
51 篇文章 0 订阅
Cache的应用非常广泛,其实Cache是一种思想,一个广义的词汇,一种在性能和金钱的权衡上的思想。
Cache的思想用在很多地方,使用的载体也不同,都是位于相对高速设备和相对低速设备之间,起到缓存的作用。
1、最常用处:用在内存和CPU之间,以SRAM作为Cache的载体。(内存是DRAM,CPU是寄存器
2、用在内存和硬盘之间。
3、用在硬盘和网络之间。

Cache的操作方法:
1、外部操作:数据访问,Get和Set。高速设备要找一个数据,先从Cache找,如果有就直接用;如果没有则去低速设备找,并调入到Cache。
2、内部操作:Cache更新。Cache的大小必定是有限的。所以需要一个淘汰算法(使得命中率尽可能高),最常用的是LRU。当Cache满了却又要加入新的数据,就需要淘汰旧的数据。

Cache的典型实现:
LRU的典型实现是  hash map + double linked list
为什么要用两种数据结构?
理由:hash表是用来访问数据的,因为hash的查找速度快,O(1)。双向链表是用来记录最近访问情况的,因为Hash做不到排序。

如果访问键值为key的数据已经在Cache中,那就访问hash中的该数据, 同时要将存储该key的结点移到双向链表头部。 
如果访问的数据不在Cache中: 
    若此时Cache还没满,那么我们将新结点插入到链表头部, 同时在哈希表添加(键值,结点地址)。
    若此时Cache已经满了,我们就将链表中的最后一个结点(注意不是尾结点)的内容替换为新内容, 然后移动到头部,更新哈希表中对应的(键值,结点地址)。

查询操作的时间是O(1),直接通过hash找到节点,而不是通过链表顺序查找的。
对链表的操作插入操作和移动操作也都是O(1)。

LRUCache简单实例(源自于Leetcode):
实现get(key) 函数:如果key对应的value存在则返回value,否则返回-1。 Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
实现set(key, value)函数:设置或插入value值。 Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

具体实现方法:
哈希表用map实现,结点存储的是key和对应的双向链表结点指针。
双向链表用list实现,结点存储的是key和value。
这样,由key可以用哈希表快速找到链表结点,进行数据访问和结点位置更新。
class LRUCache{
public:
    LRUCache(int capacity) {
        cap = capacity;
        size = 0;
        hash_table.clear();
        double_list.clear();
    }
    
    int get(int key) {
        map<int, list<pair<int, int> >::iterator>::iterator it = hash_table.find(key);

        if(it != hash_table.end())  //update double_list
        {
            int value = (*( (*it).second ) ).second; 
            double_list.splice(double_list.begin(), double_list, (*it).second);
            return value;
        }
        else //Not in Cache
        {
            return -1;
            //In fact, we should go to another place to get it.
        }
    }
    
    void set(int key, int value) {
        map<int, list<pair<int, int> >::iterator>::iterator it = hash_table.find(key);

        if(it != hash_table.end()) //update double_list and hash_table's value
        {
            double_list.splice(double_list.begin(), double_list, (*it).second);
	        (*( (*it).second ) ).second = value;
        }
        else
        {
            if(size == cap) //invalidate the lru one
            {
                int oldkey = double_list.back().first;
		        list<pair<int, int> >::iterator oldone = double_list.end();
		        oldone--;
                double_list.splice(double_list.begin(), double_list, oldone);
                
                hash_table.erase(oldkey);
		        (*double_list.begin()).first = key;
		        (*double_list.begin()).second = value;
                hash_table[key] = double_list.begin();
            }
            else // insert new one to cache
            {
                double_list.push_front(pair<int, int>(key, value));
                hash_table[key] = double_list.begin();
                size++;
            }
        }
    }
    
private:
        //注意:对双向链表的操作不要使迭代器失效
        map<int, list<pair<int, int> >::iterator> hash_table; //存储:(关键词key , 双链表中的结点指针)
        list<pair<int, int> > double_list; //结点存储:(key, value)
        int size;
        int cap;
};

注意细节:
对双向链表list的修改绝对不可以使原有的迭代器失效,否则hash里存储的内容就失效了。
这里使用的是splice方法来将中间的某个结点移动到首位置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值