一、概念
LRU缓存(Least Recently Used Cache)是一种常用的缓存淘汰算法,它基于“最近最少使用”的原则来管理缓存中的数据对象。当缓存空间不足时,LRU缓存会淘汰最久未被使用的数据,以确保缓存中始终存储着最新和最频繁使用的数据。
二、基本原理
LRU缓存的核心思想是:最近被访问的数据在未来可能会被再次访问,因此应该保留最近被访问的数据,淘汰最久未使用的数据。具体来说,LRU通过以下步骤工作:
- 缓存访问:当一个数据被访问时,算法会记录这个数据的访问时间或更新其访问顺序。
- 缓存插入:当需要将新数据插入缓存时,如果缓存尚未满,则直接插入。如果缓存已满,则使用LRU策略淘汰最久未使用的数据。
- 缓存淘汰:依据LRU策略,淘汰那些最久未被使用的数据,即那些距离上次访问时间最长的缓存项。
三、双向链表+哈希表实现
链表结点:
存储key的原因是缓存淘汰时可以获取到淘汰结点的key,从而删除哈希表中对应的值。
使用双向链表的原因是单向链表无法获取前一个元素,无法只通过结点指针删除该结点。
struct mListNode
{int key;int value;struct mListNode *next;struct mListNode *pre;mListNode():key(0),value(0),next(nullptr),pre(nullptr){}mListNode(int k,int v):key(k),value(v),next(nullptr),pre(nullptr){}
};
双向链表:
在双向链表的实现中,使用一个伪头部(dummy head)和伪尾部(dummy tail)标记界限,这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在。
缓存访问:moveToTail
缓存插入:addToTail,再判断是否超出capacity,若超过则缓存淘汰
缓存淘汰:removeOldest从表头移除元素
class DLinkedList
{
public:DLinkedList():_head(new mListNode),_tail(new mListNode),_size(0){_head->next=_tail;_tail->pre=_head;}mListNode *addToTail(int key,int value){mListNode *pNew=new mListNode(key,value);pNew->next=_tail;pNew->pre=_tail->pre;_tail->pre->next=pNew;_tail->pre=pNew;++_size;return pNew;}void removeElem(mListNode *p){p->pre->next=p->next;p->next->pre=p->pre;delete p;p=nullptr;--_size;}int removeOldest(){int key=_head->next->key;removeElem(_head->next);return key;}void show()//调试用{if(_size==0) return;mListNode *cur=_head->next;while(cur->next){cout<<cur->key<<":"<<cur->value<<" ";cur=cur->next;}cout<<"\n";}void moveToTail(mListNode *p){p->pre->next=p->next;p->next->pre=p->pre;p->next=_tail;p->pre=_tail->pre;_tail->pre->next=p;_tail->pre=p;}int size() const{return _size;}
private:mListNode *_head;mListNode *_tail;int _size;
};
LRU缓存:
class LRUCache {
public:unordered_map<int,mListNode*> cache;int cap;DLinkedList *list;
public:LRUCache(int capacity):cap(capacity),list(new DLinkedList()){}int get(int key) {if(!cache.count(key)){return -1;}mListNode *p=cache[key];list->moveToTail(p);return p->value;}void put(int key, int value) {if(cache.count(key)){mListNode *p=cache[key];p->value=value;list->moveToTail(p);}else{mListNode *p=list->addToTail(key,value);cache[key]=p;}while(cap<list->size()){int key=list->removeOldest();cache.erase(key);}}
};
四、使用std::list+std::unordered_map实现
class LRUCache {
public:unordered_map<int,std::list<pair<int,int>>::iterator> cache;int cap;list<pair<int,int>> list;
public:LRUCache(int capacity):cap(capacity),list(){}int get(int key) {if(!cache.count(key)){return -1;}auto it=cache[key];list.splice(list.begin(),list,it);return it->second;}void put(int key, int value) {if(cache.count(key)){auto it=cache[key];it->second=value;list.splice(list.begin(),list,it);cache[key]=list.begin();}else{list.push_front(make_pair(key,value));cache[key]=list.begin();}while(cap<list.size()){auto it=list.back();list.pop_back();cache.erase(it.first);}}
};