用 js 实现 FIFO, LRU, LFU 缓存淘汰算法

看了网上一些人写的缓存淘汰算法,大概明白了这三种淘汰算法的实现思路,然后自己在对这些算法的理解基础上用js语言实现如下

1. FIFO 先入先出

这个相对比较简单,使用一个数组存储,在没到达最大存储空间时,只要set就一直往数组里面push,如果满了,就先把数组最前面的使用shift给删除了后再push

class FIFOCache {
  constructor (limit) {
    this.limit = limit || 10
    this.map = {}
    this.keys = []
  }
  set (key, value) {
    let map = this.map
    let keys = this.keys
    if (!Object.prototype.hasOwnProperty(map, key)) {
      if (this.keys.length === this.limit) {
        delete map[keys.shift()]
      }
    } else {
      keys.splice(keys.indexOf(key), 1)
    }
    keys.push(key)
    map[key] = value
  }
  get (key) {
    return this.map[key]
  }
}

2. LRU 最近最少使用

LRU相对要复杂一点,根据名字可以知道缓存算法是针对时间来计算的,需要自己定义一个双向链表节点的对象,每次使用就往链表头部加一个节点,如果满了,就先删除尾部的节点后再把节点添加到链表头部,如果节点已经存在,又去使用了,就先删除与前后的联系,再把节点的前后互相连接,最后把自己移到链表头部

// 双向链表
class DoubleLinkNode {
  constructor(key, val) {
    this.key = key || null
    this.val = val || null
    this.pre = null
    this.next = null
  }
}
class LRUCache {
  constructor (limit) {
    this.limit = limit || 10
    this.head = null // 头部指针
    this.tail = null // 尾部指针
    this.map = new Map()
    this.size = 0
  }
  set (key, value) {
    let node = this.get(key)
    // 如果没找到该节点
    if (!node) {
      if (this.size === this.limit) {
        // 如果存储已经到极限了就删除最后一个节点,并把尾指针向前移动一个位置
        if (this.tail) {
          this.map.delete(this.tail.key)
          this.size--
          this.tail = this.tail.pre
          this.tail.next.pre = null
          this.tail.next = null
        }
      }
      // 新建一个节点
      node = new DoubleLinkNode()

      // 第一次set,则设置头尾节点为自己
      if (this.size === 0) {
        this.head = node
        this.tail = node
      }
      this.size++

      // 如果不是头部则移动到头部
      if (node !== this.head) {
        node.next = this.head
        this.head.pre = node
        this.head = node
      }
    }
    node.key = key
    node.val = value
    this.map.set(key, node)
  }
  get (key) {
    let node = this.map.get(key)
    if (!node) return
    // 有节点说明使用了,需要移动到链表头部
    if (node.pre && node.next) {
      // 前面有节点,后面也有节点,说明在中间位置,需要断开两边的连接,然后移到链表头部
      node.pre.next = node.next
      node.next.pre = node.pre
      node.next = this.head
      this.head.pre = node
      this.head = node
    } else if (node.pre && !node.next) {
      // 前面有节点,后面没有节点,说明在末尾的位置,需要把尾指针指向前面一个节点,然后断开前面的连接,把该节点移到链表头部
      this.tail = node.pre
      node.pre.next = null
      node.next = this.head
      node.pre = null
      this.head.pre = node
      this.head = node
    } else if (!node.pre && node.next) {
      // 前面无节点,后面有节点,说明在头部,不用做移动的操作
    } else {
      // 前后都无节点,说明只有自己一个节点,不用做移动的操作
    }
    return node
  }
}

3. LFU 最不经常使用

LFU针对的是频率这个维度,就得用到count来存储使用的次数,我就直接使用了一个数组来存储多个对象,每个对象有自己的key,value, count等数据,每次使用则count加1并且将对象根据count进行排序,如果count相同,使用时间最近的,则排在相同count的最前面。同样的,到达缓存最大值,就删除最后一个值,再将新值push进去。

class LFUCache {
  constructor (limit) {
    this.limit = limit || 10
    this.list = []
    this.size = 0
  }
  set (key, value) {
    // 一旦set,就先去get方法里增加count,重新根据优先级排序
    let item = this.get(key)
    if (item) {
      // 排好序后直接赋值
      item.value = value
    } else {
      // 数组里没有这个key=>value,则创建一个,并把count初始化为1
      let item = {
        key,
        value,
        count: 1
      }
      if (this.size > 0) { // 如果list里有值
        if (this.size === this.limit) { // 如果list存储已满,则删除最后一个
          this.list.pop()
          this.size--
        }
        for (let i = this.list.length - 1; i >= 0; i--) { // 遍历在合适的位置添加新数据
          if (this.list[i].count > 1) {
            this.list.splice(i + 1, 0, item)
            break
          }
          if (i === 0) {
            this.list.unshift(item)
          }
        }
      } else {
        this.list.unshift(item)
      }
      this.size++
    }
  }
  get (key) {
    let index = this.list.findIndex(item => { return item.key === key })
    if (index > -1) {
      // 某个值有使用则将对应count加1,然后从数组中删除,再遍历,移动到count数相同的一批值的最前面
      // 因为count数相同,则最后一次使用时间比较近的优先级最高
      let item = this.list[index]
      item.count++
      this.list.splice(index, 1)
      for (let i = this.list.length - 1; i >= 0; i--) {
        if (this.list[i].count > item.count) {
          this.list.splice(i + 1, 0, item)
          break
        }
        if (i === 0) {
          this.list.unshift(item)
        }
      }
      return item
    }
    return
  }
}

总结:针对算法,重点还是了解其思想,然后再根据自己目前的代码水平敲一敲,再测一测,加深对算法的认识和理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值