当前位置: 首页 > article >正文

前端小技巧: 实现 LRU 缓存算法功能

关于LRU缓存

  • LRU - Lease Recently Used 最近使用

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

  • 核心 api: get set

  • 分析

    • 使用哈希表来实现, O(1)
    • 必须是有序的,常用放在前面,沉睡放在后面, 即:有序,可排序
    • 这样 {} 不符合要求;Map是可以排序的,按照设置顺序
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改造成双向链表
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}
  • 上述数据结构很精妙
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()
  }
}

功能性测试

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 删除

http://www.kler.cn/news/107617.html

相关文章:

  • Kafka-Java四:Spring配置Kafka消费者提交Offset的策略
  • vue如何使用路由拦截器
  • 数据结构 C语言 2.1 线性表抽象数据类型 2.2 小议顺序表
  • Tp框架如何使用事务和锁,还有查询缓存
  • Linux UWB Stack实现——FiRa会话状态机
  • jmeter疑难杂症
  • 数据库数据恢复—Oracle数据库报错ORA-01110错误的数据恢复案例
  • Hive 常用DML操作
  • 前端移动web高级详细解析二
  • 安装虚拟机(VMware)保姆级教程及配置虚拟网络编辑器和安装WindowsServer以及宿主机访问虚拟机和配置服务器环境
  • 实体店做商城小程序如何
  • 模数转换器-ADC基础
  • 深入探究深度学习、神经网络与卷积神经网络以及它们在多个领域中的应用
  • Android-宝宝相册(第四次作业)
  • 【计算机网络】(谢希仁第八版)第一章课后习题答案
  • 软考 系统架构设计师系列知识点之设计模式(9)
  • ES6之Set集合(通俗易懂,含实践)
  • 外卖霸王餐系统 支持小程序,分站合作
  • 关于pycharm中句号变成点的问题
  • Redis 与 MySQL 一致性 实现方案
  • RSA:基于小加密指数的攻击方式与思维技巧
  • SpringCore完整学习教程5,入门级别
  • 单元测试Testng
  • vue中如何获取当时时间时分秒
  • “第五十三天”
  • 数据库批处理
  • C++学习笔记之四(标准库、标准模板库、vector类)
  • slice()和splice()用法
  • 【笔录】TVP技术沙龙:寻宝AI时代
  • dd命令用法学习,是一个功能强大的工具