数据结构与算法之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 删除
相关推荐
格林威40 分钟前
常规线扫描镜头有哪些类型?能做什么?
人工智能·深度学习·数码相机·算法·计算机视觉·视觉检测·工业镜头
程序员莫小特3 小时前
老题新解|大整数加法
数据结构·c++·算法
过往入尘土4 小时前
服务端与客户端的简单链接
人工智能·python·算法·pycharm·大模型
zycoder.4 小时前
力扣面试经典150题day1第一题(lc88),第二题(lc27)
算法·leetcode·面试
蒙奇D索大4 小时前
【数据结构】考研数据结构核心考点:二叉排序树(BST)全方位详解与代码实现
数据结构·笔记·学习·考研·算法·改行学it
智驱力人工智能5 小时前
工厂抽烟检测系统 智能化安全管控新方案 加油站吸烟检测技术 吸烟行为智能监测
人工智能·算法·安全·边缘计算·抽烟检测算法·工厂抽烟检测系统·吸烟监测
程序员爱钓鱼5 小时前
Go语言实战案例——进阶与部署篇:编写Makefile自动构建Go项目
后端·算法·go
酷ku的森5 小时前
Redis的缓存更新策略
缓存
_Power_Y6 小时前
Java面试常用算法api速刷
java·算法·面试
艾醒(AiXing-w)6 小时前
大模型面试题剖析:模型微调中冷启动与热启动的概念、阶段与实例解析
人工智能·深度学习·算法·语言模型·自然语言处理