面试官:实现一个 LRU 缓存。巧用 Map 【JavaScript】

前言

大家好,我是 PakJeon.

这是一道比较常见的面试算法题。其实原理上并不难理解,但是实现细节却有很多注意的地方,特别是常规的哈希表+双向链表的实现。这次介绍一个巧妙的方法,巧用 JavaScript 内置的 Map。

什么是LRU

LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently Used,是一种常见的缓存算法,用于管理缓存中的数据。LRU算法的核心思想是保留最近被访问过的数据,而淘汰最久未被访问的数据。

它的工作原理如下:

  1. 初始化: 缓存被初始化为一定的容量,当缓存达到容量上限时,需要淘汰数据。
  2. 访问数据: 每次访问(读取或写入)缓存中的数据时,将该数据移动到数据结构的头部或更新其位置,表示这个数据是最近被使用的。
  3. 淘汰数据: 当缓存达到容量上限时,需要淘汰最久未被访问的数据,通常是数据结构的尾部的数据。
js 复制代码
// 题目需要实现一个 LRUCache,实现以下效果,且 put、get 操作要求是时间复杂度 O(1)

const cache = new LRUCache(2); // 创建一个容量为2的 LRU 实例
console.log(cache.put(1, 1)); // 返回 null   此时cache为【1】
console.log(cache.put(2, 2)); // 返回 null   此时cache为【2,1】
console.log(cache.get(1)); // 返回 1         此时cache为【1,2】,因为使用了1,1变成"新鲜的"
console.log(cache.put(3, 3)); // 返回 null   此时cache为【3,1】,超过容量了,把"老油条"2淘汰
console.log(cache.get(2)); // 返回 -1        此时cache为【3,1】
console.log(cache.put(4, 4)); // 返回 null   此时cache为【4,3】,超过容量了,把"老油条"1淘汰
console.log(cache.get(1)); // 返回 -1        此时cache为【4,3】
console.log(cache.get(3)); // 返回 3         此时cache为【3,4】
console.log(cache.get(4)); // 返回 4         此时cache为【4,3】

JavaScript 中的 Map

JavaScript 中的 Map 是一种数据结构,用于存储键值对,并且不同于普通的对象,Map 的键可以是任意数据类型。前端同学对这个都不陌生,平常用到的setgetdeletesize等 Api 相信不用再多介绍。

这次,我们利用的是Map.prototype.keys,来解决这道算法题。

Map.prototype.keys

我们看看这个 Api 运行的结果是什么。

首先创建一个名为cache的 Map 实例,并设置了三个键值对,分别是firstsecondthird

然后调用chache.keys(),我们得到了一个MapIterator。顾名思义,"Map 迭代器",它是一个迭代器,也是本次介绍解法的重点。

关于迭代器,不熟悉的同学可以参考阮一峰老师的 ECMAScript6 入门中关于iterator的介绍

MapIterator

我们知道,迭代器iteratornext方法,每次调用都会返回一个包含valuedone属性的对象。value指的是这次迭代得到的值(在MapIterator这里, value具体指的就是每个键值对的key),而done是个布尔值,指的是元素是否全部迭代完毕。

不难发现,next方法返回的顺序,正是我们往cache中设置键值对的顺序,这就是解决问题的关键。

利用 MapIterator

既然MapIterator.next()会返回第一个设置的键值对,那刚好就是我们 LRU 缓存中的老油条 。再加上Mapgetset也都是时间复杂度O(1),完美符合我们的需求。

LRU 框架

js 复制代码
var LRUCache = function(capacity) {
    this.cap = capacity;
    this.cache = new Map();
};

LRUCache.prototype.get = function(key) {
    // 待实现
    // 分两种情况:
    // ①:cache中不存在key,返回-1
    // ②:cache中已存在key,返回对应的值,并把它变成"新鲜的"

};

LRUCache.prototype.put = function(key, val) {
    // 待实现
    // 分两种情况:
    // ①:cache中已存在key,把key的值用val覆盖,并把它变成"新鲜的";
    // ②:cache中不存在key,再分两种情况:
    // ②-①:cache的缓存区未满,则直接设置键值对;
    // ②-②:cache的缓冲区已满,把"老油条"干掉,再设置键值对。
};

// 辅助方法,把参数 key 的值变成"新鲜的"
LRUCache.prototype.makeRecently = function(key) {
    // 待实现
};

LRU 实现

有了上面的框架 + MapIterator的知识,剩下的就是往框架里填代码了。

js 复制代码
var LRUCache = function(capacity) {
    this.cap = capacity;
    this.cache = new Map();
};

LRUCache.prototype.get = function(key) {
    // ①:cache中不存在key,返回-1
    if (!this.cache.has(key)) {
        return -1;
    }
    // ②:cache中已存在key,返回对应的值,并把它变成"新鲜的"
    this.makeRecently(key);
    return this.cache.get(key);
};

LRUCache.prototype.put = function(key, val) {
    // ①:cache中已存在key,把key的值用val覆盖,并把它变成"新鲜的";
    if (this.cache.has(key)) {
        this.cache.set(key, value);
        this.makeRecently(key);
        return;
    }
    // ②:cache中不存在key,再分两种情况:
    // ②-①:cache的缓存区未满,则直接设置键值对;
    // ②-②:cache的缓冲区已满,把"老油条"干掉,再设置键值对。
    if (this.cache.size >= this.cap) {
        const oldestKey = this.cache.keys().next().value; // 利用MapIterator获取"老油条"
        this.cache.delete(oldestKey);
    }
    this.cache.set(key, value);
};

// 辅助方法,把参数 key 的值变成"新鲜的"
LRUCache.prototype.makeRecently = function(key) {
    const val = this.cache.get(key); // 用变量缓存新鲜值
    this.cache.delete(key); // 删除后再set,就会变成MapIterator的最后一项,也就是"最新鲜的"
    this.cache.set(key, val);
};

结语

希望这篇文章为你提供了对MapLRU缓存算法的深入了解,同时也能够激发你在实际项目中灵活运用这些知识的创造力。如果你有任何问题或想要深入探讨相关主题,欢迎在评论区留言,让我们共同进步!

相关推荐
张拭心5 小时前
Cursor 又偷偷更新,这个功能太实用:Visual Editor for Cursor Browser
前端·人工智能
I'm Jie5 小时前
深入了解 Vue 3 组件间通信机制
前端·javascript·vue.js
用户90443816324606 小时前
90%前端都踩过的JS内存黑洞:从《你不知道的JavaScript》解锁底层逻辑与避坑指南
前端·javascript·面试
CodeCraft Studio7 小时前
文档开发组件Aspose 25.12全新发布:多模块更新,继续强化文档、图像与演示处理能力
前端·.net·ppt·aspose·文档转换·word文档开发·文档开发api
无敌最俊朗@7 小时前
STL-vector面试剖析(面试复习4)
java·面试·职场和发展
PPPPickup7 小时前
easychat项目复盘---获取联系人列表,联系人详细,删除拉黑联系人
java·前端·javascript
老前端的功夫7 小时前
前端高可靠架构:医疗级Web应用的实时通信设计与实践
前端·javascript·vue.js·ubuntu·架构·前端框架
Benmao⁢8 小时前
C语言期末复习笔记
c语言·开发语言·笔记·leetcode·面试·蓝桥杯
前端大卫8 小时前
【重磅福利】学生认证可免费领取 Gemini 3 Pro 一年
前端·人工智能
孜燃8 小时前
Flutter APP跳转Flutter APP 携带参数
前端·flutter