什么是 LRU 算法?

缓存 是我们写代码过程中常用的一种手段,是一种空间换时间的做法。就拿我们经常使用的 HTTP 协议,其中也存在强缓存和协商缓存两种缓存方式。当我们打开一个网站的时候,浏览器会查询该请求的响应头,通过判断响应头中是否有 Cache-ControlLast-ModifiedETag 等字段,来确定是否直接使用之前下载的资源缓存,而不是重新从服务器进行下载。

下面就是当我们访问百度时,某些资源命中了协商缓存,服务端返回 304 状态码,还有一部分资源命中了强缓存,直接读取了本地缓存。

但是,缓存并不是无限制的,会有大小的限制。无论是我们的 cookie(不同浏览器有所区别,一般在 4KB 左右),还是 localStorage(和 cookie 一样,不同浏览器有所区别,有些浏览器为 5MB,有些浏览器为 10MB),都会有大小限制。

这个时候就需要涉及到一种算法,需要将超出大小限制的缓存进行淘汰,一般的规则是淘汰掉最近没有被访问到的缓存,也就是今天要介绍的主角:LRULeast recently used:最近最少使用)。当然除了 LRU,常见的缓存淘汰还有 FIFO(first-in, first-out:先进先出) 和 LFU(Least frequently used:最少使用)。

什么是 LRU?

LRULeast recently used:最近最少使用)算法在缓存写满的时候,会根据所有数据的访问记录,淘汰掉未来被访问几率最低的数据。也就是说该算法认为,最近被访问过的数据,在将来被访问的几率最大。

为了方便理解 LRU 算法的全流程,画了一个简单的图:

  1. 假设我们有一块内存,一共能够存储 5 数据块;
  2. 依次向内存存入A、B、C、D、E,此时内存已经存满;
  3. 再次插入新的数据时,会将在内存存放时间最久的数据A淘汰掉;
  4. 当我们在外部再次读取数据B时,已经处于末尾的B会被标记为活跃状态,提到头部,数据C就变成了存放时间最久的数据;
  5. 再次插入新的数据G,存放时间最久的数据C就会被淘汰掉;

算法实现

下面通过一段简单的代码来实现这个逻辑。

class LRUCache {
    list = [] // 用于标记先后顺序
    cache = {} // 用于缓存所有数据
    capacity = 0 // 缓存的最大容量
    constructor (capacity) {
    // 存储 LRU 可缓存的最大容量
        this.capacity = capacity
    }
}

基本的结构如上所示,LRU需要实现的就是两个方法:getput

class LRUCache {
  // 获取数据
    get (key) { }
  // 存储数据
    put (key, value) { }
}

我们现在看看如何进行数据的存储:

class LRUCache {
  // 存储数据
    put (key, value) {
    // 存储之前需要先判断长度是否达到上限
    if (this.list.length >= this.capacity) {
      // 由于每次存储后,都会将 key 放入 list 最后,
      // 所以,需要取出第一个 key,并删除cache中的数据。
            const latest = this.list.shift()
            delete this.cache[latest]
        }
    // 写入缓存
        this.cache[key] = value
    // 写入缓存后,需要将 key 放入 list 的最后
        this.list.push(key)
  }
}

然后,在每次获取数据时,都需要更新 list,将当前获取的 key 放到 list 的最后。

class LRUCache {
  // 获取数据
    get (key) {
        if (this.cache[key] !== undefined) {
        // 如果 key 对应的缓存存在
      // 在返回缓存之前,需要重新激活 key
            this.active(key)
            return this.cache[key]
        }
        return undefined
  }
  // 重新激活key,将指定 key 移动到 list 最后
    active (key) {
    // 先将 key 在 list 中删除
        const idx = this.list.indexOf(key)
        if (idx !== -1) {
            this.list.splice(idx, 1)
    }
    // 然后将 key 放到 list 最后面
        this.list.push(key)
    }
}

这个时候,其实还没有完全实现,因为除了 get 操作,put 操作也需要将对应的 key 重新激活。

class LRUCache {
  // 存储数据
    put (key, value) {
        if (this.cache[key]) {
            // 如果该 key 之前存在,将 key 重新激活
            this.active(key)
            this.cache[key] = value
      // 而且此时缓存的长度不会发生变化
      // 所以不需要进行后续的长度判断,可以直接返回
            return
        }

    // 存储之前需要先判断长度是否达到上限
    if (this.list.length >= this.capacity) {
      // 由于每次存储后,都会将 key 放入 list 最后,
      // 所以,需要取出第一个 key,并删除cache中的数据。
            const latest = this.list.shift()
            delete this.cache[latest]
        }
    // 写入缓存
        this.cache[key] = value
    // 写入缓存后,需要将 key 放入 list 的最后
        this.list.push(key)
  }
}

可能会有人觉得这种算法在前端没有什么应用场景,说起来,在 Vue 的内置组件 keep-alive 中就使用到了 LRU 算法。

后续应该还会继续介绍一下 LFU 算法,敬请期待……


自然醒的笔记本
学习过程中的一些总结和沉淀,欢迎关注公众号「自然醒的笔记本」
3.9k 声望
6.8k 粉丝
0 条评论
推荐阅读
详解 Webpack devtools
最近在开发一个低代码平台,主要用于运营搭建 H5 活动。这中间涉及到第三方组件的开发,而第三方组件想要接入平台,需要经过我们特定的打包工具来 build。构建之后的组件,会合并成单个的 js 文件,而且代码会被...

Shenfq阅读 724

手把手教你写一份优质的前端技术简历
不知不觉一年一度的秋招又来了,你收获了哪些大厂的面试邀约,又拿了多少offer呢?你身边是不是有挺多人技术比你差,但是却拿到了很多大厂的offer呢?其实,要想面试拿offer,首先要过得了简历那一关。如果一份简...

tonychen153阅读 17.9k评论 5

封面图
正则表达式实例
收集在业务中经常使用的正则表达式实例,方便以后进行查找,减少工作量。常用正则表达式实例1. 校验基本日期格式 {代码...} {代码...} 2. 校验密码强度密码的强度必须是包含大小写字母和数字的组合,不能使用特殊...

寒青57阅读 8.5k评论 11

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy49阅读 7.2k评论 12

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs42阅读 6.9k评论 12

封面图
CSS 绘制一只思否猫
欢迎关注我的公众号:前端侦探练习 CSS 有一个比较有趣的方式,就是发挥想象,绘制各式各样的图案,比如来绘制一只思否猫?思否猫,SegmentFault 思否的吉祥物,是一只独一无二、特立独行、热爱自由的(>^ω^&lt...

XboxYan47阅读 3.3k评论 14

封面图
「多图预警」完美实现一个@功能
一天产品大大向 boss 汇报完研发成果和产品业绩产出,若有所思的走出来,劲直向我走过来,嘴角微微上扬。产品大大:boss 对我们的研发成果挺满意的,balabala...(内心 OS:不听,讲重点)产品大大:咱们的客服 I...

wuwhs32阅读 3.5k评论 5

封面图
3.9k 声望
6.8k 粉丝
宣传栏