LRU缓存

一、什么是 LRU 缓存

LRU(Least Recently Used)缓存是一种缓存淘汰策略,用于在缓存容量有限的情况下,决定哪些数据需要被移除。其核心思想是:

  • 最近最少使用:当缓存满了,需要移除最久未被使用的数据,以给新的数据腾出空间。

二、LRU 缓存的实现原理

为了实现 LRU 缓存,需要满足以下两个要求:

  1. 快速访问缓存中的数据:需要在 O(1) 的时间复杂度内查找和更新缓存中的数据。
  2. 维护数据的使用顺序:需要按照数据被访问的先后顺序来更新缓存。

为此,通常使用 哈希表(HashMap)双向链表(Doubly Linked List) 的组合来实现:

  • 哈希表:用于在 O(1) 时间内快速查找缓存中的数据。
  • 双向链表:用于按访问顺序记录数据,从而在 O(1) 时间内完成插入和删除操作。

三、哈希表与双向链表的组合实现

1. 数据结构设计

  • 哈希表(Map<K, Node>)

    • Key:缓存数据的键。
    • Value:链表节点的引用(Node)。
  • 双向链表

    • 头节点(Head):最近使用的节点。
    • 尾节点(Tail):最久未使用的节点。

2. 操作流程

  • 访问数据(Get 操作)

    1. 在哈希表中查找数据:
      • 如果存在:
        • 将对应的节点移动到链表的头部(表示最近使用)。
        • 返回节点的值。
      • 如果不存在:
        • 返回 -1 或表示未找到。
  • 添加/更新数据(Put 操作)

    1. 在哈希表中查找数据:
      • 如果存在:
        • 更新节点的值。
        • 将节点移动到链表的头部。
      • 如果不存在:
        • 如果缓存已满:
          • 移除链表尾部的节点(最久未使用的节点)。
          • 从哈希表中删除对应的键。
        • 创建新节点,添加到链表头部。
        • 在哈希表中添加键和节点的映射。

3. 关键点

  • 双向链表

    • 之所以使用双向链表,是因为在删除节点时,需要在 O(1) 时间内完成。单向链表无法在 O(1) 时间内获取前驱节点。
  • 哈希表与链表的结合

    • 哈希表用于快速定位节点。
    • 链表用于维护节点的使用顺序。

四、底层实现原理-相对于源代码

import java.util.HashMap;
import java.util.Map;

public class LRUCache {
    class Node {
        int key;
        int value;
        Node prev;
        Node next;
        public Node() {}
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    private Map<Integer, Node> cache = new HashMap<>();
    private int size;
    private int capacity;
    private Node head, tail;

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        // 初始化双向链表的头尾节点
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.prev = head;
    }

    // 获取数据
    public int get(int key) {
        Node node = cache.get(key);
        if (node == null) {
            return -1; // 未找到
        }
        // 移动节点到头部
        moveToHead(node);
        return node.value;
    }

    // 添加或更新数据
    public void put(int key, int value) {
        Node node = cache.get(key);
        if (node == null) {
            // 创建新节点
            Node newNode = new Node(key, value);
            // 添加到哈希表
            cache.put(key, newNode);
            // 添加到链表头部
            addToHead(newNode);
            size++;
            if (size > capacity) {
                // 移除尾节点
                Node tail = removeTail();
                // 从哈希表中删除
                cache.remove(tail.key);
                size--;
            }
        } else {
            // 更新值
            node.value = value;
            // 移动到头部
            moveToHead(node);
        }
    }

    // 辅助方法:添加节点到头部
    private void addToHead(Node node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    // 辅助方法:移除节点
    private void removeNode(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    // 辅助方法:移动节点到头部
    private void moveToHead(Node node) {
        removeNode(node);
        addToHead(node);
    }

    // 辅助方法:移除尾节点
    private Node removeTail() {
        Node res = tail.prev;
        removeNode(res);
        return res;
    }
}

五、Demo Code

直接使用

public class Main {
    public static void main(String[] args) {
        LRUCache lruCache = new LRUCache(2); // 缓存容量为 2
        lruCache.put(1, 1); // 缓存为 {(1,1)}
        lruCache.put(2, 2); // 缓存为 {(2,2),(1,1)}
        System.out.println(lruCache.get(1)); // 返回 1,缓存为 {(1,1),(2,2)}
        lruCache.put(3, 3); // 缓存已满,移除最久未使用的键 2,缓存为 {(3,3),(1,1)}
        System.out.println(lruCache.get(2)); // 返回 -1(未找到)
        lruCache.put(4, 4); // 缓存已满,移除键 1,缓存为 {(4,4),(3,3)}
        System.out.println(lruCache.get(1)); // 返回 -1(未找到)
        System.out.println(lruCache.get(3)); // 返回 3
        System.out.println(lruCache.get(4)); // 返回 4
    }
}

六、常见的 LRU 缓存应用场景

LRU(Least Recently Used,最近最少使用)缓存是一种策略,用于在存储空间有限的情况下,优先保留最近使用过的数据,移除长时间未使用的数据。下面是几个日常生活中常见的应用场景

  1. 网页浏览器缓存

    • 场景:当你浏览网页时,浏览器会存储(缓存)你访问过的网页内容,如图片、样式等。
    • 作用:下次访问相同网站时,加载速度更快,因为浏览器可以直接使用缓存的数据。
    • LRU 应用:浏览器的缓存空间有限,当缓存满了,浏览器会删除最久未访问的网站缓存,腾出空间给新的网页。
  2. 手机应用的数据缓存

    • 场景:许多手机应用(如新闻、社交媒体)会缓存你浏览过的内容。
    • 作用:提高应用的响应速度,节省流量。
    • LRU 应用:当缓存空间不足时,应用会删除你长时间未查看的内容,优先保留最近浏览的内容。
  3. 操作系统的内存管理

    • 场景:计算机的内存(RAM)容量有限,无法同时容纳所有正在运行的程序和数据。
    • 作用:操作系统需要在内存和硬盘之间调度数据,以保证系统的流畅运行。
    • LRU 应用:操作系统会将长时间未使用的数据从内存移到硬盘(称为虚拟内存),当需要腾出空间时,优先保留最近使用的数据。
  1. 智能手机的后台应用管理

    • 场景:手机同时运行多个应用,但内存有限,无法全部保留在后台。
    • 作用:保证前台应用的流畅运行。
    • LRU 应用:系统会根据应用的最近使用情况,关闭长时间未使用的后台应用,释放内存资源。

LRU 缓存在各种设备和应用中帮助管理有限的资源,确保系统高效运行,同时提升用户体验。

总结

  • LRU 缓存通过哈希表和双向链表的组合,实现了在 O(1) 时间复杂度内完成数据的读取和更新,同时维护了数据的使用顺序。
  • 哈希表用于快速定位节点,双向链表用于记录节点的使用顺序,实现快速的插入和删除操作。
  • 这种设计在实际工程中非常常见,适用于需要缓存最近使用数据的场景,如数据库缓存、内存管理等。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容