浅析LRU缓存淘汰算法

35 阅读2分钟

LRU缓存淘汰算法

  • LRU(Least Recently Used)最近最少使用算法
  • 我们程序在运行中,难免会有很多缓存,缓存可以方便我们快速获取响应的数据,但是当缓存过多时,也会占用内存空间。而LRU算法则是一种缓存优化手段,我们可以把常用的缓存保留,而不常用的缓存给清楚掉。
  • 具体算法的解释大家可以查阅相关资料,我就直接上代码了,基于hashMap + 双链表实现(O1)复杂度的LRU算法
class ListNode {
  val: any;
  pre: IListNode = null;
  next: IListNode = null;
  key: any;
  constructor(val: any, key = null) {
    this.val = val;
    this.key = key;
  }
}

type IListNode = ListNode | null;

export class LRUmini {
  #map: Map<any, ListNode>;
  #capacity: number;
  #head: IListNode = null;
  #end: IListNode = null;

  constructor(capacity) {
    this.#capacity = capacity;
    this.#map = new Map();
  }

  put(key, value) {
    let node = this.#map.get(key);
    if (node) {
      // 当node节点存在
      // 说明当前当前我们只需要修改node的值
      // 并将node插入到头部
      node.val = value;
      this.inserHeader(node);
    } else {
      // 当节点不存在时,创建节点
      node = new ListNode(value, key);
      // 如果链表还没有数据时,即初始化当前的head end 都指向该node节点
      if (this.#map.size === 0) {
        this.#head = node;
        this.#end = node;
      } else {
        // 否则将当前节点插入到头部
        this.inserHeader(node);
      }
      // 将节点放入hashMap中
      this.#map.set(key, node);

      // 因为是新增的节点,所以在每次新增节点后判断节点是否溢出
      if (this.#map.size > this.#capacity) {
        // 如果溢出则从map中删除尾部节点
        this.#map.delete(this.#end!.key);
        // 同时更新end的节点
        this.#end = this.#end!.pre;
        this.#end!.next = null;
      }
    }
  }

  get(key) {
    const node = this.#map.get(key);

    // 如果node不存在 直接返回null
    if (node === undefined) {
      return null;
    }

    // 将当前的激活的节点放置到头部
    this.inserHeader(node);

    return node;
  }
  // 将节点放置到头部
  inserHeader(node) {
    // 如果当前节点已经在头部 跳过该操作
    if (node === this.#head) return;

    // 如果节点在尾部 先将尾部的end指针指向上一个节点
    if (node === this.#end) {
      this.#end = this.#end!.pre;
    }

    // 将node的pre节点合next节点相连 即断开当前node节点
    if (node.pre) {
      node.pre.next = node.next;
    }
    if (node.next) {
      node.next.pre = node.pre;
    }

    // 将当前node节点插入到头部
    node.pre = null;
    node.next = this.#head;
    this.#head!.pre = node;
    this.#head = node;
  }

  get head() {
    return this.#head;
  }

  get end() {
    return this.#end;
  }
}