LRU算法

1.什么是LRU算法

LRU算法又称最近最少使用算法,它的基本思想是长期不被使用的数据,在未来被用到的几率也不大,所以当新的数据进来时我们可以优先把这些数据替换掉。

在LRU算法中,使用了一种有趣的数据结构,称为哈希链表。我们知道哈希表是由多个<Key,Value>对组成的,哈希链表是将这写节点链接起来,每一个节点都有一个前驱结点和后驱节点,就像双向链表中的节点一样。哈希表拥有了固定的排列顺序。

在这里插入图片描述

基于哈希链表的有序性,我们就可以把<Key,Value>按照最后的使用时间来排列。

2.LRU算法的基本思路

假设我们使用哈希链表来缓存用户信息,目前缓存了4个用户,用户按照时间顺序从链表右端插入:

在这里插入图片描述

情景一:当访问用户5时,由于哈希链表中没有用户5的数据,从数据库中读取出来插入到缓存中

在这里插入图片描述

情景二:挡访问用户2时,由于哈希链表中有用户2的数据,我们把它掐断,放到链表最右段

在这里插入图片描述

情景三:同情景二,这次访问用户4的数据

在这里插入图片描述

情景四:当用户访问用户6,用户6在缓存中没有,需要插入到链表中,但此时链表长度已满,我们把最左端的用户删掉,然后插入用户6
在这里插入图片描述

说明:我们仔细回顾一下,当缓存命中时,我们就把它放到最右端,也就是说排在右边的是最近被使用过的,那左边的当然是相对较少被访问过的,所以当缓存不命中的时候,我们就把最左边的剔除掉,所以这里就体现了最近最少使用的原则。

3.LRU算法的基本实现

// 此方法 是将最近使用最多的放在最左端,较少的放在最右端
class LRUCache {
    class Node {
    	// 键和值
        int k, v;
        // 左右结点
        Node l, r;
        Node(int _k, int _v) {
            k = _k;
            v = _v;
        }
    }
    int n;
    Node head, tail;// 头结点和尾结点
    Map<Integer, Node> map;
    public LRUCache(int capacity) {
        n = capacity;
        map = new HashMap<>();
        head = new Node(-1, -1);
        tail = new Node(-1, -1);
        head.r = tail;
        tail.l = head;
    }
 
    public int get(int key) {
        if (map.containsKey(key)) {
            Node node = map.get(key);
            refresh(node);
            return node.v;
        }
        return -1;
    }
 
    public void put(int key, int value) {
        Node node = null;
        if (map.containsKey(key)) {
            node = map.get(key);
            node.v = value;
        } else {
            if (map.size() == n) {
                Node del = tail.l;
                map.remove(del.k);
                delete(del);
            }
            node = new Node(key, value);
            map.put(key, node);
        }
        refresh(node);
    }
 
    // refresh 操作分两步:
    // 1. 先将当前节点从双向链表中删除(如果该节点本身存在于双向链表中的话)
    // 2. 将当前节点添加到双向链表头部
    void refresh(Node node) {
        delete(node);
        node.r = head.r;
        node.l = head;
        head.r.l = node;
        head.r = node;
    }
 
    // delete 操作:将当前节点从双向链表中移除
    // 由于我们预先建立 head 和 tail 两位哨兵,因此如果 node.l 不为空,则代表了 node 本身存在于双向链表(不是新节点)
    void delete(Node node) {
        if (node.l != null) {
            Node left = node.l;
            left.r = node.r;
            node.r.l = left;
        }
    }
}
### LRU缓存算法概述 LRU(Least Recently Used)是一种常见的缓存淘汰策略,其核心思想是移除最长时间未被使用的数据项。该算法假设未来可能不会再次使用过去较长时间未曾访问的数据[^3]。 在实际应用中,LRU缓存通常用于优化性能敏感的操作环境,比如数据库查询、文件系统缓冲以及内存管理等场景。它能够在固定大小的缓存空间内高效存储和检索数据,在时间和空间之间取得平衡。 --- ### Java中的LRU缓存实现详解 #### 使用`LinkedHashMap`实现 Java提供了内置工具类`LinkedHashMap`来简化LRU缓存的设计。通过重写`removeEldestEntry()`方法,可以控制何时删除最早插入或最少使用的条目。以下是具体代码示例: ```java import java.util.LinkedHashMap; import java.util.Map; public class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int capacity; public LRUCache(int capacity) { super(capacity, 0.75f, true); // accessOrder设为true表示按照访问顺序排列 this.capacity = capacity; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > capacity; // 当超出容量时移除最早的条目 } } ``` 此实现利用了`LinkedHashMap`的特性——可以通过设置参数`accessOrder=true`让映射表按访问顺序维护键值对。每次调用`get()`或`put()`都会更新对应条目的位置,从而确保最近使用的条目始终位于链表头部[^2]。 测试代码展示了如何创建并操作一个简单的LRU缓存实例: ```java public static void main(String[] args) { LRUCache<Integer, String> cache = new LRUCache<>(3); cache.put(1, "one"); cache.put(2, "two"); cache.put(3, "three"); System.out.println(cache); // 输出: {1=one, 2=two, 3=three} cache.get(2); System.out.println(cache); // 输出: {1=one, 3=three, 2=two} (由于访问了key=2) cache.put(4, "four"); System.out.println(cache); // 输出: {3=three, 2=two, 4=four} (key=1因超过容量而被淘汰) } ``` --- ### C++中的LRU缓存实现 除了Java外,C++也可以用来构建高效的LRU缓存结构。一种常见的方式是结合哈希表(std::unordered_map)与双向链表(std::list)。这种方法能够分别提供快速查找能力和灵活调整节点次序的能力。 下面是一个基于标准库容器的手动实现版本: ```cpp #include <iostream> #include <unordered_map> #include <list> template<typename K, typename V> class LRUCache { private: std::list<std::pair<K, V>> lruList; // 双向链表保存元素及其顺序 std::unordered_map<K, typename std::list<std::pair<K, V>>::iterator> map; // 哈希表加速查找 const size_t capacity; void moveToFront(const K& key) { auto it = map[key]; lruList.splice(lruList.begin(), lruList, it); // 将目标移到列表前端 } public: explicit LRUCache(size_t cap): capacity(cap){} V get(const K& key){ if(map.find(key)==map.end()) throw std::out_of_range("Key not found."); moveToFront(key); return (*map[key]).second; } void put(const K& key, const V& value){ if(map.size()==capacity && map.count(key)==0){ K last = lruList.back().first; lruList.pop_back(); map.erase(last); } if(map.count(key)!=0){ lruList.erase(map[key]); } lruList.emplace_front(std::make_pair(key,value)); map[key]=lruList.begin(); } }; ``` 这段程序定义了一个通用模板类`LRUCache`,支持任意类型的键值对存储,并且严格遵循LRU原则处理溢出情况[^3]。 --- ### 性能分析 无论是采用Java还是C++方式实现,上述两种方案均能在平均情况下达到O(1)的时间复杂度完成基本功能。这是因为它们都依赖于底层具有常数级读写的抽象数据类型作为支撑:前者依靠的是`LinkedHashMap`内部机制;后者则借助了散列函数定位加双端队列重组的优势组合[^1]。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Listen·Rain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值