一. 缓存可以提高数据读取性能的技术, 比如 CPU 缓存、数据库缓存、浏览器缓存等等。
二. 缓存的大小有限,当缓空间存被用满时,哪些数据应该被清理出去,哪些数据应该被保留?这是需要缓存淘汰策略决定。我了解到的有如下几种:
- 粗暴的先进先出策略 FIFO(First In,First Out)这里可以用类似浏览器的max-age, 或者干脆用队列保存push,shift.
- 最少使用策略 LFU(Least Frequently Used)
- 最近最少使用策略 LRU(Least Recently Used)
三. 核心思想是: “如果数据最近被访问过,那么将来被访问的几率也更高”
由于使用数组在头部插入元素需要的步数多,性能不好,所以采用链表来实现,顺带简单实现一下React Fiber的遍历思想。 首先我们先要实现功能完备一个链表:
function NodeList () {
this.head = new Node("head")
this.length = 0 // 链表长度
}
NodeList.prototype.findNode = function (item) {
let curNode = this.head
while (curNode.data !== item) {
curNode = curNode.next
}
return curNode
}
NodeList.prototype.deleteNode = function (item) {
let curNode = this.head
while (curNode.next !== null & curNode.next.data !== item) {
curNode = curNode.next
}
if (curNode.next !== null) {
curNode.next = curNode.next.next
}
}
NodeList.prototype.insertNode = function (newData, data) {
const newNode = new Node(newData)
const findNode = this.findNode(data)
newNode.next = findNode.next
findNode.next = newNode
this.length++
}
NodeList.prototype.consoleDisplay = function () {
let curNode = this.head
let num = 0
while (curNode.next !== null) {
num++
console.log(num + '. ', curNode.next.data)
curNode = curNode.next
}
}
function Node (data) {
this.data = data
this.next = null
}
const nodelist = new NodeList()
console.log('链表', nodelist)
nodelist.insertNode("名字", 'head')
nodelist.insertNode("性别", '名字')
nodelist.insertNode("年龄 ", '性别')
// 看看链表怎么排的
nodelist.consoleDisplay()
/**
* 测试下打印结果:
* 链表 NodeList { head: Node { data: 'head', next: null }, length: 0 }
* 1. 名字
* 2. 性别
* 3. 年龄
**/
四. 有了链表之后,我们再梳理下LRU缓存的过程。
- 如果数据存在于链表中,遍历获取这个节点,并将其从原位置删除,添加到链表的头部。
- 如果数据不存在于链表中,此时有两种可能:
- 缓存未满,直接将新数据添加到链表头部。
- 缓存已满,删除尾部节点,新数据添加到链表头部
- 直接上代码,先声明一个LRU缓存类, 以及更新缓存空间的方法
function LRUCache(maxSpace) {
this.nodelist = new NodeList()
this.cacheSpace = this.nodelist.length
this.maxSpace = maxSpace
}
LRUCache.prototype.updateSpace = function () {
this.cacheSpace = this.nodelist.length
return this
}
- 然后定义输入新内容的缓存方法cacheNode
LRUCache.prototype.cacheNode = function (item) {
if (this.nodelist.findNode(item) !== null) {
// 1. 如果数据存在于链表中,遍历获取这个节点,并将其从原位置删除,添加到链表的头部。
this.nodelist.deleteNode(item).insertNode(item, 'head')
} else {
// 2. 如果数据不存在于链表中,此时有两种可能:
if (this.cacheSpace < this.maxSpace) {
// 2.1 缓存未满,直接将新数据添加到链表头部。
this.nodelist.insertNode(item, 'head')
} else {
// 2.2 缓存已满,删除尾部节点,新数据添加到链表头部
console.log("缓存已满,自动删除访问量最低的")
this.deleteLast().insertNode(item, 'head')
}
}
// 更新缓存空间
this.updateSpace()
return this
}
- 当遇到缓存空间满了的情况下,我们需要删除链表的尾部项
LRUCache.prototype.deleteLast = function () {
let curNode = this.nodelist.head
let preNode = null
while(curNode.next !== null && curNode.next.data !== null) {
preNode = curNode
curNode = curNode.next
}
console.log(JSON.stringify(preNode), 1111111)
// 倒数第二个节点next指针等于null
preNode.next = null
return this.nodelist
}
- 最后定义打印缓存内容的方法
LRUCache.prototype.display = function () {
console.log('缓存最大空间' + this.maxSpace + '个')
this.nodelist.consoleDisplay()
}
const cache_LRU = new LRUCache(10) // 链表实体最长10个
五. 哈哈,代码搞定,到了我们最期待的验证环节
- 先输入名字,看打印结果。名字已经被缓存进入,并且打印最大缓存空间
cache_LRU.cacheNode("名字")
cache_LRU.display()
// 缓存最大空间10个
// 1. 名字
- 输入性别,发现性别排在了名字的前边
cache_LRU.cacheNode("名字")
cache_LRU.cacheNode("性别")
cache_LRU.display()
// 缓存最大空间10个
// 1. 性别
// 2. 名字
- 输入年龄,发现年龄又被替换到了链表的头部,性别随后,最后是名字
cache_LRU.cacheNode("名字")
cache_LRU.cacheNode("性别")
cache_LRU.cacheNode("年龄")
cache_LRU.display()
// 缓存最大空间10个
// 1. 年龄
// 2. 性别
// 3. 名字
- 这是输入一个已经有的名字, 看看名字是否会被从新加载到头部,变成Least Recently Used。
cache_LRU.cacheNode("名字1")
cache_LRU.cacheNode("名字2")
cache_LRU.cacheNode("名字3")
cache_LRU.cacheNode("名字4")
cache_LRU.cacheNode("名字5")
cache_LRU.cacheNode("名字6")
cache_LRU.cacheNode("名字7")
cache_LRU.cacheNode("名字8")
cache_LRU.cacheNode("名字9")
cache_LRU.cacheNode("名字10")
cache_LRU.cacheNode("名字11")
cache_LRU.display()
// 缓存最大空间10个
// 1. 名字
// 2. 年龄
// 3. 性别
- 最后一步,验证缓存满了会不会,淘汰最不常用的。发现名字1已经被缓存淘汰了,最后输入的名字11排在第一个
cache_LRU.cacheNode("名字1")
cache_LRU.cacheNode("名字2")
cache_LRU.cacheNode("名字3")
cache_LRU.cacheNode("名字4")
cache_LRU.cacheNode("名字5")
cache_LRU.cacheNode("名字6")
cache_LRU.cacheNode("名字7")
cache_LRU.cacheNode("名字8")
cache_LRU.cacheNode("名字9")
cache_LRU.cacheNode("名字10")
cache_LRU.cacheNode("名字11")
cache_LRU.display()
// 缓存最大空间10个
// 1. 名字11
// 2. 名字10
// 3. 名字9
// 4. 名字8
// 5. 名字7
// 6. 名字6
// 7. 名字5
// 8. 名字4
// 9. 名字3
// 10. 名字2