LRU缓存

一、概念

LRU缓存(Least Recently Used Cache)是一种常用的缓存淘汰算法,它基于"最近最少使用"的原则来管理缓存中的数据对象。当缓存空间不足时,LRU缓存会淘汰最久未被使用的数据,以确保缓存中始终存储着最新和最频繁使用的数据。

二、基本原理

LRU缓存的核心思想是:最近被访问的数据在未来可能会被再次访问,因此应该保留最近被访问的数据,淘汰最久未使用的数据。具体来说,LRU通过以下步骤工作:

  1. 缓存访问:当一个数据被访问时,算法会记录这个数据的访问时间或更新其访问顺序。
  2. 缓存插入:当需要将新数据插入缓存时,如果缓存尚未满,则直接插入。如果缓存已满,则使用LRU策略淘汰最久未使用的数据。
  3. 缓存淘汰:依据LRU策略,淘汰那些最久未被使用的数据,即那些距离上次访问时间最长的缓存项。

三、双向链表+哈希表实现

链表结点:

存储key的原因是缓存淘汰时可以获取到淘汰结点的key,从而删除哈希表中对应的值。

使用双向链表的原因是单向链表无法获取前一个元素,无法只通过结点指针删除该结点。

cpp 复制代码
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从表头移除元素

cpp 复制代码
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缓存:

cpp 复制代码
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实现

cpp 复制代码
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);
        }
    }
};
相关推荐
草莓熊Lotso6 小时前
【Redis 进阶】主从复制深度解析:从配置落地到 PSYNC 同步原理
linux·开发语言·网络·数据库·redis·缓存·php
程序员阿鹏1 天前
如何实现MySQL分库分表?
数据结构·数据库·sql·mysql·缓存
爱和冰阔落1 天前
【Linux】条件变量为什么必须配合互斥锁:从 pthread_cond_wait 到阻塞队列
linux·运维·c++·缓存·中间件·安卓
wdfk_prog1 天前
VMware Ubuntu 虚拟机从 124 GB 压缩到 33 GB
运维·缓存·docker·容器
刃神太酷啦1 天前
Redis 核心进阶:哨兵、集群、缓存问题与分布式锁详解----《Hello Redis!》(6)
linux·c语言·数据库·c++·redis·分布式·缓存
名字还没想好☜1 天前
Python 的 __call__ 实战:让实例像函数一样被调用,做带状态计数器、缓存器与可配置策略
开发语言·后端·python·缓存·编程语言
像风一样自由20201 天前
29.Redis在大模型应用中有哪些用途缓存会话与限流
数据库·人工智能·redis·缓存·大模型·milvus·智能体
九皇叔叔2 天前
【第二章】Redis基础入门:Redis是什么、为什么快以及核心特性详解
数据库·redis·缓存
敲代码的嘎仔2 天前
用 Redis 合并写 + DelayQueue 延迟检测,把视频播放进度写库频率降到 1/60
java·开发语言·数据库·redis·缓存·音视频·高并发
程序猿乐锅2 天前
【黑马点评 | 第一篇】从 Session 到 Redis+Token 的改造与拦截器实现
数据库·redis·缓存