前端使用JavaScript实现一个LRU缓存

news2024/9/24 19:22:12

引言

LRU(Least Recently Used)算法是一种广泛应用于内存管理和缓存系统的策略,在微前端、状态管理以及性能优化等场景下,合理使用缓存机制能够有效提升应用性能。本文将介绍LRU算法的基本原理,并通过JavaScript实现案例,帮助读者理解其在前端开发中的应用场景。

LRU算法原理

LRU(最近最少使用)算法是一种常用的缓存淘汰策略,它假定“最近最久未使用的数据在未来被访问的可能性最小”。当缓存空间不足时,LRU会优先移除最近最少使用的数据,为新数据腾出存储空间。

实现方式

哈希表适合快速查找、插入和删除的场景,而双向链表适合频繁插入和删除节点的场景。在某些情况下,这两种数据结构也可以结合实现LRU缓存算法时,可以使用哈希表存储 key 和对应的节点,双向链表存储实际的数据节点,以实现快速的查找和插入删除操作。

图解示例

假设设计一个容量为3的LRU缓存

  1. 首先添加3个元素
  2. 哈希表中依次添加数据的key值:key1,key2,key3
  3. 双向链表存储哈希对(key,value),key3是最后一个添加的(最新添加记录是key3),那么key3对应的哈希值添加到链表的头部node3。表示最近使用的。
    在这里插入图片描述

这个时候如果要添加第四个数据,key4放入哈希表,node4放入双向链表头部,node4包含key4,value4,然而此时容量不足,需要删除一个元素,从尾部删除一个最久没使用的元素,删除上面图示中的node1的数据,同时在哈希表中删除node1对应的key1
把node4添加到链表头部,key4添加进哈希表是同步进行的。
在这里插入图片描述

如果最近要访问key3,需要把node3从当前位置删除,并插入到链表头部
在这里插入图片描述

简而言之:

每次添加元素到链表中的时候都是从头部添加

每次删除元素的时候都是从尾部删除

删除的时候同时从哈希表里面删除对应的key

再次访问的元素,需要把元素移动到链表的头部

实现代码

使用哈希链表,可以在每个缓存项的节点上同时存储键值信息以及指向链表中前后节点的引用。当一个缓存项被访问时,先通过哈希表找到对应节点,然后将其从原有位置移出并插入链表尾部

class LRUCacheNode {
  constructor(key, value) {
    this.key = key;
    this.value = value;
    this.prev = null;
    this.next = null;
  }
}

class LRUCache {
  constructor(capacity = 500) {
    this.capacity = capacity;
    this.cacheMap = new Map(); // 使用哈希表存储键值对
    this.doubleLinkedList = new DoublyLinkedList(); // 双向链表维护缓存顺序
  }

  get(key) {
    if (this.cacheMap.has(key)) {
      const node = this.cacheMap.get(key);
      this.doubleLinkedList.moveToTail(node); // 将节点移动到链表尾部,表示最新访问
      return node.value;
    }
    return -1; // 或者返回null,表示key不存在于缓存中
  }

  put(key, value) {
    if (this.cacheMap.has(key)) {
      const node = this.cacheMap.get(key);
      node.value = value;
      this.doubleLinkedList.moveToTail(node);
    } else {
      if (this.cacheMap.size >= this.capacity) {
        const headNode = this.doubleLinkedList.deleteHead();
        this.cacheMap.delete(headNode.key); // 移除最旧的缓存项
      }
      const newNode = new Node(key, value);
      this.cacheMap.set(key, newNode);
      this.doubleLinkedList.addToTail(newNode);
    }
  }
}

// 双向链表类和节点类的实现略(根据实际需求实现)
class Node {
  constructor(key, value) {
    this.key = key; // 节点键值
    this.value = value; // 节点数据值
    this.prev = null; // 前驱节点引用
    this.next = null; // 后继节点引用
  }
}
class DoublyLinkedList {
  constructor() {
    this.head = null; // 头节点
    this.tail = null; // 尾节点
  }

  /**
   * 添加节点到链表尾部
   * @param {Node} newNode 新节点
   */
  addToTail(newNode) {
    if (!this.head) {
      this.head = newNode;
      this.tail = newNode;
    } else {
      newNode.prev = this.tail;
      this.tail.next = newNode;
      this.tail = newNode;
    }
  }

  /**
   * 移除头节点并返回
   * @returns {Node | null} 删除的头节点或null(如果链表为空)
   */
  deleteHead() {
    if (!this.head) return null;

    const deletedNode = this.head;
    this.head = this.head.next;

    if (this.head) {
      this.head.prev = null;
    } else {
      this.tail = null;
    }

    return deletedNode;
  }

  /**
   * 将指定节点移动到链表尾部
   * @param {Node} node 需要移动的节点
   */
  moveToTail(node) {
    if (node === this.tail) return; // 如果已经是尾节点,则无需移动

    // 断开当前节点与前后节点的连接
    node.prev.next = node.next;
    if (node.next) node.next.prev = node.prev;

    // 将节点添加至链表尾部
    this.addToTail(node);
  }
  
  // 其他可能的方法,如查找节点、在指定位置插入节点等...
}


使用场景

路由缓存:Vue.js 中的 keep-alive 组件虽然并未直接采用LRU算法,但在实际项目中,我们可以基于LRU策略自定义实现路由组件的缓存功能。
资源加载:对于频繁请求且响应较慢的API,可以通过LRU缓存最近请求的结果,减少网络请求次数。
状态管理:在Vuex或Redux等状态管理库中,也可以利用LRU算法进行缓存,避免频繁计算或获取昂贵的状态。
业务场景:电商大促,浏览器浏览历史,微博热点。实现的具体可能不同,但是思路均可使用LRU缓存实现.

网页浏览历史

实现一个简单的浏览历史记录功能

class LRUCache {
    constructor(capacity) {
        this.capacity = capacity;
        this.cache = new Map();
    }

    get(key) {
        if (this.cache.has(key)) {
            const value = this.cache.get(key);
            // 删除旧数据再重新插入,以更新最近访问的顺序
            this.cache.delete(key);
            this.cache.set(key, value);
            return value;
        }
        return null;
    }

    put(key, value) {
        if (this.cache.has(key)) {
            this.cache.delete(key);
        } else if (this.cache.size >= this.capacity) {
            // 超出容量时删除最久未访问的数据(即最近使用频率最低的数据)
            const keys = this.cache.keys();
            this.cache.delete(keys.next().value);
        }
        this.cache.set(key, value);
    }

    displayHistory() {
        console.log("Browser History:");
        for (let [key, value] of this.cache) {
            console.log(key + " -> " + value);
        }
    }
}

// 使用示例
const historyCache = new LRUCache(5); // 设置缓存容量为5

historyCache.put("Page 1", "www.page1.com");
historyCache.put("Page 2", "www.page2.com");
historyCache.put("Page 3", "www.page3.com");
historyCache.get("Page 1");

// 输出浏览历史记录
historyCache.displayHistory();

在上面的示例中,LRUCache 类实现了一个简单的 LRU 缓存,通过 get 方法获取历史记录,并通过 put 方法添加历史记录。displayHistory 方法用于展示浏览历史记录。

可以根据实际需求进一步扩展和优化这个示例,比如添加时间戳来记录访问时间、持久化历史记录到本地存储等功能。希望这个示例能帮助你实现浏览历史记录功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1706202.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL(进阶)--索引

目录 一.存储引擎 1.MySQL体系结构​编辑 2.存储引擎简介 3.存储引擎特点 (1.InnoDB (2.MyISAM (3.Memory 4.存储引擎选择 二.索引 1.索引概述 2.索引结构 3.索引分类 4.索引语法 (1.创建索引 (2.查看索引 (3.删除索引 5.SQL性能分析 (1.SQL执行频率 (2.慢查…

Linux——Dockerfile部分参数(1)

在这里我们来整理一下docker容器、dockerfile、docker镜像的关系: dockerfile是面向开发的,发布项目做镜像的时候就要编写dockerfile文件。 dockerfile:构建文件,定义了一切的步骤,源代码。 dockerImanges&#xff1a…

为什么Facebook Marketplace无法使用?如何解决?

Facebook Marketplace是一个允许用户买卖商品的平台,由于其在Facebook内的便捷性,它逐渐成为了一个受欢迎的在线交易市场。然而,做Facebook跨境电商,很多人会面临的情况就是无法使用Facebook Marketplace。这到底是什么原因&#…

PSexec工具横向移动

一. 其他横向工具和命令介绍 之前我们讲解了IPC,PTH,PTK,PTH,其实这些东西都是用来进行认证的 IPC:使用明文的账号密码进行认证 PTH:使用NTLM-hash值进行认证 PTK:使用AES秘钥进行认证 PTT:使用票据进行认证认证通过之后如何实现远程上线内网其他的机器&#xff0…

23种设计模式全面总结 | 快速复习(附PDF+MD版本)

本篇文章是对于23种设计模式的一个全面的总结,受限于文章篇幅无法对每个设计模式做到全面的解析,但几乎每个设计模式都提供了案例和类图结构,非常适合快速复习和在学习设计模式之前的全预习把握。 💡文章的 pdf markdown 版本可通…

智能座舱-车载声学技术训练营

语音交互赋能车载智能终端,成为智能座舱生态构建的核心功能 曾几何时,至少十年前,车内语音交互,大家都认为是“智障”阶段,基本上除了难用作为评价找不到其他的形容词做修饰。 但是随着技术的不断发展,特别…

2024年哪款充电宝好?热门罗马仕、西圣、倍思充电宝全方面实测

在这个人人都离不开手机的时代,出门坐车需要用到手机刷卡,吃饭点外卖需要用到手机,做旅游攻略需要用到手机等等,如果是出门一天的话不带充电宝,手机基本是不能够撑回到家的,这时候就需要一个充电宝辅助我们…

整理前端新出的操作工具好用又好玩(Custom Formatter,Oxlint,Nuxt DevTools,component-party)

1.使用Custom Formatter 使vue3中的reactive object 在Chrome在console中更易理解的方式展现 启用步骤: 1.打开控制台,然后打开console设置 2.前往proferences中的Console,勾选Enable custom formatters选项 3.刷新页面 2.使用css Overv…

【NumPy】权威指南:使用NumPy的percentile函数进行百分位数计算

🧑 博主简介:阿里巴巴嵌入式技术专家,深耕嵌入式人工智能领域,具备多年的嵌入式硬件产品研发管理经验。 📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向…

2024最新(PC+WEB+IOS+Android)即时通讯系统客户端仿默往IM源码下载

2024最新(PCWEBIOSAndroid)即时通讯系统客户端仿默往IM源码下载(总大小约2.4G) 系统功能配置灵活、海量并发、稳定可靠、数据安全,2小时快速部署、数据安全、单聊群聊、系统通知等通信功能,支持App、PC、Web等多端快速接入。 群功能&#xf…

【小米手环7】表盘修改/制作指南

2024年了还有人用小米手环7么? 5月10号得到我的小米手环7nfc,随之开启了我对表盘制作的探索之旅。~历时18天,终于让我成功修改了官方表盘——荧光电子!表盘:羽球人的荧光电子已上传至表盘自定义工具app。 接下来&…

将x减到0的最小操作(滑动窗口)

算法原理: 第一眼看到这个题的时候,我真的想不到它到底是怎么和滑动窗口联系起来的,在我的脑海里只有一个简单的双指针。直到我看过了题解,才明白原来是这么回事:先看题目是求最小操作数,此时你一定不要先…

二叉树顺序结构实现【堆的实现】【详细图解】

P. S.:以下代码均在VS2019环境下测试,不代表所有编译器均可通过。 P. S.:测试代码均未展示头文件stdio.h的声明,使用时请自行添加。 目录 1、二叉树的顺序结构2、堆的概念3、堆的实现3.1 堆实现的前提3.1.1 向上调整3.1.2 向下调…

网络安全行为可控定义以及表现内容简述

在数字化快速发展的今天,网络安全已成为国家和企业不可或缺的防线。据统计,网络攻击事件频发,给全球经济带来了巨大损失。因此,确保网络安全行为可控显得尤为重要。今天我们来聊聊网络安全行为可控定义以及表现内容。 网络安全行为…

音视频开发9 FFmpeg 解复用框架说明,重要API说明

一,播放器框架 二 常用音视频术语 容器/文件(Conainer/File): 即特定格式的多媒体文件, 比如mp4、flv、mkv等。 媒体流(Stream): 表示时间轴上的一段连续数据&#xff0…

维护课堂纪律的重要性

在课堂上,老师们是否经常遇到这样的情况:孩子们交头接耳,小动作不断,甚至有人偷偷玩手机?这些行为长让老师感到头疼,但作为老师,是否思考过,维护课堂纪律的重要性究竟何在&#xff1…

生信网络学院|05月31日《SOLIDWORKS Manage 产品周期管理》

课程主题:SOLIDWORKS Manage 产品周期管理 课程时间:2024年05月31日 14:00-14:30 主讲人:付舰 生信科技 PLM实施顾问 1、SOLIDWORKS Manage介绍 2、周期流程管理 3、产品项目管理 4、项目会议管理 5、项目问题管理 安装腾讯会议客户端…

java配置文件解析yml/xml/properties文件

XML 以mybatis.xml:获取所有Environment中的数据库并连接session为例 import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException;import javax.xml.parsers.DocumentBuilder; impo…

第十二周 5.21面向对象的三大特性(封装、继承、多态)(二)

三、多态 1.理解: (1)多态:父类型的引用存储不同子类型的对象 父类类名 引用名 new 子类类名(); 引用 对象 父类型 子类型 …

视频太大怎么压缩变小 视频太大了怎么压缩

视频作为一种多媒体形式,在当今社会的重要性日益凸显,其应用范围广泛,影响力深远。 但是视频文件的大小也在不断增加,这给存储和传输带来了很大的困扰。那么,当视频文件过大时,我们该如何将其压缩变小呢&am…