LRU缓存

LRU缓存

其实LRU不难实现,但是要实现高效的LRU比较难,就是要实现增删查改均为O(1)

理解核心思想

想象LRU缓存就像一个只能放3本书的小书架

  • 最近看过的书要放在最左边
  • 当书架满了要加新书时,把最右边(最久没看)的书扔掉

数据结构的作用(非常简单!)

  • list<pair<int, int>> cache :就是那个小书架 ,按阅读顺序放书 list.front()= 最近看的(最左边) list.back()= 最久没看的(最右边)
  • unordered_map<int, ...> map :是书的索引卡片 通过书名(key)快速找到书在书架上的位置 值类型是list<pair<int, int>>::iterator= 书在书架上的位置指针

关键操作拆解(对应现实动作)

1. get操作:找书看

复制代码
int get(int key) {
    // 1. 先查索引卡:这本书在书架上吗?
    auto it = map.find(key);  // it是指向索引卡条目的指针
    if (it == map.end()) return -1; // 没这本书
    
    // 2. 找到书了!把这本书抽出来放到最左边
    // cache.splice(位置, 书架, 书的位置) = 把书从当前位置移到最前面
    cache.splice(cache.begin(), cache, it->second);
    
    // 3. 返回书的内容
    return it->second->second; // it->second是书的位置,->second是书的内容
}

2. put操作:放书

复制代码
void put(int key, int value) {
    // 情况1:书已存在(更新)
    auto it = map.find(key);
    if (it != map.end()) {
        it->second->second = value; // 更新书的内容
        cache.splice(cache.begin(), cache, it->second); // 移到最前面
        return;
    }
    
    // 情况2:新书,但书架满了
    if (cache.size() == cap) {
        // 扔掉最右边的书
        int key_to_del = cache.back().first; // 最旧书的书名
        map.erase(key_to_del);  // 从索引卡中撕掉这张卡片
        cache.pop_back();       // 把书从书架最右边扔掉
    }
    
    // 把新书放到最左边
    cache.push_front({key, value}); // 书架上放新书
    map[key] = cache.begin();      // 索引卡记录:书名->书的位置(最左边)
}

迭代器到底是什么?(超简单理解)

list::iterator就想象成书签手指

  • 指向链表中的某个具体位置
  • it->second就是通过书签拿到指向的那本书

完整模拟过程(cap=2)

复制代码
操作序列:put(1,1), put(2,2), get(1), put(3,3)

1. put(1,1): 书架=[(1,1)], 索引卡:1->指向(1,1)
2. put(2,2): 书架=[(2,2),(1,1)], 索引卡:1->指向(1,1), 2->指向(2,2)  
3. get(1): 找到书1,移到最前:书架=[(1,1),(2,2)]
4. put(3,3): 书架已满,扔掉最右边的(2,2)
   - 先更新索引卡:删除key=2
   - 书架变成[(1,1)],然后插入(3,3)到最前:[(3,3),(1,1)]

class LRUCache {
private:
    int cap;
    list<pair<int, int>> cache; // 书架:存放(key,value)对
    unordered_map<int, list<pair<int, int>>::iterator> map; // 索引卡:key->书的位置

public:
    LRUCache(int capacity) : cap(capacity) {}

    int get(int key) {
        // 在索引卡中找这本书
        auto map_it = map.find(key);
        if (map_it == map.end()) return -1; // 没找到
        
        // map_it->second 是书在书架上的位置(迭代器)
        // 把这本书移到书架最前面
        cache.splice(cache.begin(), cache, map_it->second);
        
        // 返回书的内容:map_it->second指向书,->second是value
        return map_it->second->second;
    }

    void put(int key, int value) {
        auto map_it = map.find(key);
        
        if (map_it != map.end()) {
            // 书已存在:更新内容并移到最前
            map_it->second->second = value; // 更新书的内容
            cache.splice(cache.begin(), cache, map_it->second);
            return;
        }
        
        // 新书,但书架满了
        if (cache.size() == cap) {
            // 扔掉最旧的书(书架最后面)
            pair<int, int> old_book = cache.back(); // 最旧的书
            map.erase(old_book.first);  // 从索引卡删除
            cache.pop_back();           // 从书架扔掉
        }
        
        // 插入新书到书架最前面
        cache.push_front({key, value});
        // 更新索引卡:记录新书的位置(书架最前面)
        map[key] = cache.begin();
    }
};

关键记住

  • list是物理书架,存实际数据
  • unordered_map是索引卡,只存位置指针
  • 所有操作都要同时更新书架和索引卡
相关推荐
忧郁蓝调262 小时前
Redis不停机数据迁移:基于 redis-shake 的跨实例 / 跨集群同步方案
运维·数据库·redis·阿里云·缓存·云原生·paas
奋斗べ青年.4 小时前
【redis】了解redis的主从和集群搭建
数据库·redis·缓存
五阿哥永琪7 小时前
Caffeine 本地缓存
缓存
optimistic_chen8 小时前
【Redis 系列】常用数据结构---String类型
数据结构·数据库·redis·缓存·string
大猫子的技术日记8 小时前
Redis 快速上手实战教程:从零搭建高性能缓存系统
数据库·redis·缓存
修炼室8 小时前
Freebase + Virtuoso 大规模导入实战:切片 Chunk、调大缓存、脚本化监控进度(可复现)
缓存
风筝在晴天搁浅9 小时前
hot100 146.LRU缓存
java·缓存
予枫的编程笔记20 小时前
Redis 核心数据结构深度解密:从基础命令到源码架构
java·数据结构·数据库·redis·缓存·架构
周胡杰21 小时前
鸿蒙preferences单多例使用,本地存储类
缓存·华为·harmonyos·preferences·鸿蒙本地存储
爱丽_1 天前
MyBatis事务管理与缓存机制详解
数据库·缓存·mybatis