数据结构与算法之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 删除
相关推荐
会员源码网12 小时前
使用`mysql_*`废弃函数(PHP7+完全移除,导致代码无法运行)
后端·算法
木心月转码ing12 小时前
Hot100-Day10-T438T438找到字符串中所有字母异位词
算法
HelloReader13 小时前
Wi-Fi CSI 感知技术用无线信号“看见“室内的人
算法
颜酱16 小时前
二叉树分解问题思路解题模式
javascript·后端·算法
qianpeng89717 小时前
水声匹配场定位原理及实验
算法
董董灿是个攻城狮1 天前
AI视觉连载8:传统 CV 之边缘检测
算法
AI软著研究员1 天前
程序员必看:软著不是“面子工程”,是代码的“法律保险”
算法
FunnySaltyFish2 天前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
颜酱2 天前
理解二叉树最近公共祖先(LCA):从基础到变种解析
javascript·后端·算法
地平线开发者2 天前
SparseDrive 模型导出与性能优化实战
算法·自动驾驶