挑战刷leetcode第13天(链表之LRU)

133 阅读4分钟

大家好,我是哪吒,今天我要带大家进入一个神奇的世界——LRU缓存!你可能要问了:“哪吒,你不是忙着打妖怪吗?怎么搞起代码来了?” 哎,别提了,最近天庭的服务器老是卡顿,玉帝让我优化一下缓存机制,于是我决定用Java实现一个LRU缓存。没想到,这一搞,竟然让我悟出了“坚持就是胜利”的真谛!


什么是LRU缓存?

LRU,全称Least Recently Used,翻译过来就是“最近最少使用”。它的核心思想是:如果缓存满了,就踢掉那个最久没被使用的数据。就像天庭的蟠桃园,桃子太多了,玉帝说:“哪吒,把最久没人吃的桃子摘掉,给新桃子腾地方!” 于是,我就开始了我的LRU缓存之旅。


哪吒的第一招:继承LinkedHashMap

一开始,我觉得这事简单,直接用Java的LinkedHashMap不就行了?于是,我写下了这样的代码:

class LRUCache extends LinkedHashMap<Integer, Integer> {
    private int capacity;

    public LRUCache(int capacity) {
        super(capacity, 0.75F, true); // 调用父类构造方法
        this.capacity = capacity;
    }
   
    public int get(int key) {
        return super.getOrDefault(key, -1);
    }

    public void put(int key, int value) {
        super.put(key, value);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return size() > capacity; // 如果缓存满了,踢掉最老的
    }
}

你看,短短几行代码就搞定了!LinkedHashMap自带了一个“按访问顺序排序”的功能,只需要重写removeEldestEntry方法,就能实现LRU缓存。玉帝看了直呼:“哪吒,你这招真省事!”


哪吒的第二招:手写双向链表

可是,玉帝突然又说:“哪吒,你这代码太依赖Java库了,能不能自己实现一个?” 我一听,这不就是让我手写一个缓存机制吗?行,咱哪吒可不是吃素的!于是,我撸起袖子,开始手写双向链表

class LRUCache {
    class DLinkNode {
        int key;
        int val;
        DLinkNode next;
        DLinkNode pre;

        public DLinkNode(int key, int val) {
            this.key = key;
            this.val = val;
        }

        public DLinkNode() {}
    }

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

    public LRUCache(int capacity) {
        this.capacity = capacity;
        head = new DLinkNode();
        tail = new DLinkNode();
        head.next = tail;
        tail.pre = head;
    }

    public int get(int key) {
        DLinkNode node = cache.get(key);
        if (node == null) return -1; // 如果不存在,返回-1
        moveToHead(node); // 移到链表头部,表示最近使用
        return node.val;
    }

    public void put(int key, int value) {
        DLinkNode node = cache.get(key);
        if (node == null) {
            DLinkNode newNode = new DLinkNode(key, value);
            cache.put(key, newNode);
            addToHead(newNode); // 新节点加到头部
            size++;
            if (size > capacity) {
                DLinkNode tailNode = removeTail(); // 如果满了,移除尾部节点
                cache.remove(tailNode.key);
                size--;
            }
        } else {
            node.val = value; // 如果存在,更新值并移到头部
            moveToHead(node);
        }
    }

    private void addToHead(DLinkNode node) {
        node.pre = head;
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
    }

    private void removeNode(DLinkNode node) {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    private void moveToHead(DLinkNode node) {
        removeNode(node);
        addToHead(node);
    }

    private DLinkNode removeTail() {
        DLinkNode node = tail.pre;
        removeNode(node);
        return node;
    }
}

你看,这次我可是从头到尾自己实现的!用了一个双向链表来维护缓存顺序,再用一个HashMap来快速查找节点。虽然代码量多了不少,但玉帝看了直点头:“哪吒,你这招虽然费劲,但确实学到了真本事!”


哪吒的思考:坚持的意义

通过这次LRU缓存的实现,我深刻体会到了坚持的意义。一开始,我觉得用LinkedHashMap就够了,但玉帝让我手写实现,我才发现,原来自己动手才能真正理解其中的奥妙。就像我当初学法术一样,光看师父演示是不够的,还得自己一遍遍练习,才能掌握精髓。

所以,各位程序员朋友们,不要怕麻烦,不要怕困难。每一次的坚持,都会让你离“大牛”更近一步!就像我哪吒,从一个小毛孩成长为天庭的顶梁柱,靠的就是这股不服输的劲儿!


知识增量:LRU缓存的应用场景

最后,给大家科普一下LRU缓存的应用场景:

  1. 浏览器缓存:浏览器会用LRU算法缓存最近访问的网页,加快加载速度。
  2. 数据库缓存:数据库会用LRU缓存最近查询的数据,减少磁盘IO。
  3. 操作系统页面置换:操作系统会用LRU算法决定哪些内存页面该被换出。

你看,LRU缓存无处不在,学好它,你就是缓存界的“哪吒”!


总结

今天,我哪吒用Java实现了LRU缓存,从偷懒用LinkedHashMap到手写双向链表,一路坚持,终于搞定了这个难题。希望大家也能像我一样,不怕困难,坚持到底!下次再见,我要去优化天庭的数据库了,拜拜!

(PS:如果你觉得这篇文章有用,记得给我点个赞哦!不然我就用混天绫把你绑起来!