Vue 内置组件 keep-alive 中 LRU 缓存淘汰策略和实现

LRU(Least Recently Used,最近最少使用)是通过记录缓存项的访问顺序来决定淘汰的策略:当缓存满时,移除最久未被使用的项。

核心概念:

  • 缓存存储 :使用 Map 存储键值对,用于快速访问缓存内容。
  • 访问顺序跟踪 :通过 双向链表(LinkedList) 跟踪缓存的访问顺序,访问过的元素会被移动到链表的前端,最久未访问的元素位于链表的尾部。
  • 淘汰机制:缓存容量满时,删除链表尾部的元素,即最少使用的元素。

代码实现:

  1. LRUCache :负责管理缓存,使用 Map 存储键值对,使用 LinkedList 跟踪访问顺序。
  2. LinkedList:双向链表,用于按访问顺序排列缓存项,支持移动元素到链表前端、删除尾部元素等操作。
  3. Node:链表节点,用于存储缓存项。

工作方式:

  • get(key):如果缓存存在,移至链表前端,表示最近使用。
  • put(key, value):插入新缓存或更新现有缓存,满时淘汰尾部项。

代码示例:

javascript 复制代码
// 双向链表节点定义
class Node {
  constructor(value) {
    this.value = value; // 节点值
    this.prev = null;   // 指向前一个节点
    this.next = null;   // 指向后一个节点
  }
}

// LRUCache 类实现 LRU 缓存
class LRUCache {
  constructor(maxSize) {
    this.maxSize = maxSize;
    this.cache = new Map(); // Map 存储缓存,保证插入顺序
    this.list = new LinkedList();// 链表记录访问顺序
  }
 
  // 获取缓存值	
  get(key) {
    if (this.cache.has(key)) { // 如果缓存中存在该 key
      this.list.moveToFront(key); // 将该 key 移动到链表头
      return this.cache.get(key); // 返回缓存值
    }
    return null; // 如果没有找到,返回 null
  }
  
  // 插入或更新缓存
  put(key, value) {
    // 如果缓存中已经有该 key,将该项移到链表前端
    if (this.cache.has(key)) {
      this.list.moveToFront(key);
    } else {
      if (this.cache.size >= this.maxSize) { // 如果缓存已满
        // 移除尾部的最久未使用项,从缓存中删除该项
        const lastKey = this.list.removeLast();
        this.cache.delete(lastKey);
      }
      this.list.addAtFront(key); // 将新项添加到链表头
    }
    this.cache.set(key, value); // 更新缓存的值
  }
}

// 双向链表实现
class LinkedList {
  constructor() {
    // 初始化链表为空
    this.head = this.tail = null;  // 初始化链表为空
  }
 
  // 将节点插入到链表前端
  addAtFront(value) {
    const newNode = new Node(value); // 创建新节点
    if (this.head) { // 1.如果链表非空:新节点的 next 指向当前头节点。当前头节点的 prev 指向新节点
      newNode.next = this.head;
      this.head.prev = newNode;
    } else {
      this.tail = newNode; // 2.如果链表为空,新的节点是尾节点
    }
    this.head = newNode; // 3.新节点为头节点
  }
 
  // 将某个节点移动到链表头
  moveToFront(value) {
    const node = this.find(value); // 查找节点
    if (node === this.head) return; // 如果该节点已在头部,直接返回   
    // 更新 node 的前后节点,使其从链表中移除。
   	// 当前节点的前一个节点 node.prev 的 next 指针,指向当前节点的下一个节点 node.next
    if (node.prev) node.prev.next = node.next;
    // 当前节点的下一个节点 node.next 的 prev 指针,指向 当前节点的前一个节点 node.prev
    if (node.next) node.next.prev = node.prev;

	// 如果是尾节点,更新尾节点
    if (this.tail === node) this.tail = node.prev; 
    // 将节点插入到链表前端
    this.addAtFront(value);
  }
 
  // 移除链表尾部节点并返回其 key
  removeLast() {
    const value = this.tail.value; // 获取尾部节点的值
   
    if(this.head === this.tail) {  // 如果链表只有一个节点
	  this.head = this.tail = null // 清空链表
	} else {
	  this.tail = this.tail.prev // 更新尾节点
	  this.tail.next = null // 断开尾节点与原节点的链接
	}
    return value; // 返回移除值
  }
 
  // 查找链表中某个节点
  find(value) {
    let current = this.head;
    // 遍历链表,找到目标节点
    while (current && current.value !== value) {
      current = current.next;
    }
    return current;
  }
}

注释解释

  • LRUCache 类:实现了缓存的主要功能,包括存储数据、获取数据、添加新数据、以及缓存淘汰。其中 get 和 put 方法:实现了缓存读取和更新逻辑,并保证缓存的顺序和容量。
  • LinkedList 类:双向链表,用来维护缓存访问顺序。链表的头节点表示最近使用的缓存项,尾节点表示最久未使用的缓存项。
  • Node 类:链表中的节点,每个节点包含值以及指向前后节点的指针。

操作示例:

javascript 复制代码
const cache = new LRUCache(3);
cache.put('key1', 'value1');
cache.put('key2', 'value2');
cache.put('key3', 'value3');
cache.get('key1'); // 最近使用过,移到前端
cache.put('key4', 'value4'); // 超过最大容量,移除最后使用的key3

LRU 缓存淘汰策略总结:

  1. 缓存项访问顺序:每次访问都会将该缓存项移到前端,表示最近使用。
  2. 满缓存时淘汰:如果缓存超出最大容量,淘汰链表尾部的最久未访问项(LRU)。
  3. 效率Map 提供 O(1) 时间复杂度的缓存存取,LinkedList 保证了 O(1) 的插入、移动和删除操作。

记忆要点:

  • LRU:最近最少使用的缓存项会被淘汰。
  • 双向链表:用来维护缓存项的访问顺序,确保 O(1) 时间复杂度。
  • 缓存淘汰:超出容量时,淘汰最久未访问的缓存项。
相关推荐
lichenyang45313 小时前
从 Express 老项目到 NestJS + Docker:一次车辆管理系统的渐进式重构
前端
Momo__15 小时前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
程序员小富15 小时前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇15 小时前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇15 小时前
React中的forwardRef
前端·react.js·面试
槑有老呆15 小时前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
风骏时光牛马15 小时前
Verilog开发常见问题汇总解析
前端
子兮曰15 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端
weedsfly15 小时前
语法糖褪去之后——Babel 转译产物中的 JavaScript 本貌
前端·javascript