数据结构与算法之LRU: 实现 LRU 缓存算法功能 (Javascript版)

关于LRU缓存

  • LRU - Lease Recently Used 最近使用

  • 如果内存优先,只缓存最近使用的,删除 '沉睡' 数据

  • 核心 api: get set

  • 分析

    • 使用哈希表来实现, O(1)
    • 必须是有序的,常用放在前面,沉睡放在后面, 即:有序,可排序
    • 这样 {} 不符合要求;Map是可以排序的,按照设置顺序
ts 复制代码
class LRUCache {
  private length: number
  private data: Map<any, any> = new  Map()

  constructor(length: number) {
    if (length < 1) throw new Error('invalid length')
    this.length = length
  }

  set(key: any, value: any) {
    const data = this.data
    if (data.has(key)) {
      data.delete(key)
    }
    data.set(key, value)
    if (data.size > this.length) {
      // 如果超出了容量,则删除 Map 最老的元素
      const delKey = data.keys().next().value
      data.delete(delKey)
    }
  }

  get(key: any): any {
    const data = this.data
    if(!data.has(key)) return null
    const value = data.get(key)
    data.delete(key)
    data.set(key, value) // 保持最新的
    return value
  }
}

const lruCache = new LRUCache(2)
lruCache.set(1, 1) // { 1=1 }
lruCache.set(2, 2) // { 1=1,  2=2 }
lruCache.get(1) // 1 {2=2, 1=1}
lruCache.set(3,3) // {1=1, 3=3}
lruCache.get(2) // null
lruCache.set(4,4) // {3=3, 4=4}
lruCache.get(1) // null
lruCache.get(3) // {4=4, 3=3}
lruCache.get(4) // 4 {3=3, 4=4}

不用 Map 如何实现 LRU 缓存

  • 注意:有序
  • 结合 Object + Array
  • 将Array改造成双向链表
ts 复制代码
const obj1 = {value: 1, key: 'a'}
const obj2 = {value: 2, key: 'b'}
const obj3 = {value: 3, key: 'c'}

const data = [obj1, obj2, obj3]
const map =  {'a': obj1, 'b': obj2, 'c': obj3}
  • 上述数据结构很精妙
ts 复制代码
interface IListNode {
  value: any
  key: string
  prev?: IListNode
  next?: IListNode
}

export default class LRUCache {
  private length: number
  private data: { [key: string]: IListNode } = {}
  private dataLength: number = 0
  private listHead: IListNode | null = null
  private listTail: IListNode | null = null

  constructor(length: number) {
    if (length < 1) throw new Error('invalid length')
  }

  private moveToTail(curNode: IListNode) {
    const tail = this.listTail
    if (tail === curNode) return
    // 1. 让 pre next 断绝与 curNode的关系
    const preNode = curNode.prev
    const nextNode = curNode.next
    if (prevNode) {
      if (nextNode) {
        preNode.next = nextNode
      } else {
        delete prevNode.next
      }
    }
    if (nextNode) {
      if (prevNode) {
        nextNode.prev = prevNode
      } else {
        delete nextNode.prev
      }
      if (this.listHead === curNode) this.listHead = nextNode
    }

    // 2. 让 curNode 断绝与 prev, next的关系
    delete curNode.prev
    delete curNode.next
    // 3. 在list 末尾重新建立 curNode 的新关系
    if (tail) {
      tail.next = curNode
      curNode.prev = tail
    }
    this.listTail = curNode
  }

  private tryClean() {
    while(this.dataLength > this.length) {
      const head = ths.listHead
      if (!head) throw new Error('head is null')
      const headNext = head.next
      if (!headNext) throw new Error('headNext is null')
      // 1. 断绝head和next的关系
      delete headNext.prev
      delete head.next
      // 2. 重新赋值 listHead
      this.listHead = headNext
      // 3. 清理 data, 重新计数
      delete this.data[head.key]
      this.dataLength -= 1
    }
  }

  get(key: string):any {
    const data = this.data
    const curNode  = data[key]
    if (!curNode) return null
    if (this.listTail === curNode) {
      reutrn curNode.vlaue
    }

    // curNode 移动到末尾
    this.moveToTail(curNode)
  }
  set(key: string, value: any) {
    const data = this.data
    const curNode  = data[key]
    if (!curNode) {
      // 新增数据
      const newNode: IListNode = {key, value}
      // 移动到末尾
      this.moveToTail(newNode)
      data[key] = newNode
      this.dataLength ++
      if (this.dataLength === 1) this.listHead = newNode
    } else {
      // 修改现有数据
      curNode.value = value
      // 移动到末尾
      this.moveToTail(curNode)
    }
    // 长度超过了,则需要清理
    this.tryClean()
  }
}

功能性测试

ts 复制代码
const lruCache = new LRUCache(2)
lruCache.set('1', 1) // { 1=1 }
lruCache.set('2', 2) // { 1=1,  2=2 }
lruCache.get('1') // 1 {2=2, 1=1}
lruCache.set('3',3) // {1=1, 3=3}
lruCache.get('2') // null
lruCache.set('4',4) // {3=3, 4=4}
lruCache.get('1') // null
lruCache.get('3') // {4=4, 3=3}
lruCache.get('4') // 4 {3=3, 4=4}
  • 数据结构设计:data 和 list 分别存储什么
  • 双向链表的操作非常繁琐,代码很容易写错, 不易调试
  • 链表 node 要存储 node.key, 否则需要遍历 data 删除
相关推荐
吃着火锅x唱着歌1 天前
LeetCode 1128.等价多米诺骨牌对的数量
算法·leetcode·职场和发展
十八岁讨厌编程1 天前
【算法训练营 · 补充】LeetCode Hot100(中)
算法·leetcode
橘颂TA1 天前
【剑斩OFFER】算法的暴力美学——最小覆盖字串
算法·c/c++·就业
wearegogog1231 天前
基于混合蛙跳算法和漏桶算法的无线传感器网络拥塞控制与分簇新方法
网络·算法
Tiandaren1 天前
大模型应用03 || 函数调用 Function Calling || 概念、思想、流程
人工智能·算法·microsoft·数据分析
2301_795167201 天前
玩转Rust高级应用 如何进行理解Refutability(可反驳性): 模式是否会匹配失效
开发语言·算法·rust
小当家.1051 天前
[LeetCode]Hot100系列.贪心总结+思想总结
算法·leetcode·职场和发展
墨雪不会编程1 天前
数据结构—排序算法篇二
数据结构·算法·排序算法
ShineWinsu1 天前
对于数据结构:堆的超详细保姆级解析—上
数据结构·c++·算法·计算机·二叉树·顺序表·
im_AMBER1 天前
Leetcode 46
c语言·c++·笔记·学习·算法·leetcode