看了网上一些人写的缓存淘汰算法,大概明白了这三种淘汰算法的实现思路,然后自己在对这些算法的理解基础上用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
}
}
总结:针对算法,重点还是了解其思想,然后再根据自己目前的代码水平敲一敲,再测一测,加深对算法的认识和理解。