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


    关于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}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    不用 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}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 上述数据结构很精妙
    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()
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    功能性测试

    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}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 数据结构设计:data 和 list 分别存储什么
    • 双向链表的操作非常繁琐,代码很容易写错, 不易调试
    • 链表 node 要存储 node.key, 否则需要遍历 data 删除
  • 相关阅读:
    [Python]mini-Web框架
    Spring循环依赖
    idea 没加载 provided的包
    14款DevOps/SRE工具,助力提升运维效率
    生活不止诗和远方,还有​眼前的苟且!
    【总结】kubernates 插件工具总结
    AlphaFold2源码解析(8)--模型之三维坐标构建
    Git子模块使用说明
    单应用多语言切换(语言国际化)
    K8S自建LoadBalancer
  • 原文地址:https://blog.csdn.net/Tyro_java/article/details/134097112