JS 實現(xiàn)緩存算法的示例(FIFO/LRU)
FIFO
最簡單的一種緩存算法,設置緩存上限,當達到了緩存上限的時候,按照先進先出的策略進行淘汰,再增加進新的 k-v 。
使用了一個對象作為緩存,一個數(shù)組配合著記錄添加進對象時的順序,判斷是否到達上限,若到達上限取數(shù)組中的第一個元素key,對應刪除對象中的鍵值。
/**
* FIFO隊列算法實現(xiàn)緩存
* 需要一個對象和一個數(shù)組作為輔助
* 數(shù)組記錄進入順序
*/
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.call(map,key)) {
if (keys.length === this.limit) {
delete map[keys.shift()]//先進先出,刪除隊列第一個元素
}
keys.push(key)
}
map[key] = value//無論存在與否都對map中的key賦值
}
get(key){
return this.map[key]
}
}
module.exports = FifoCache
LRU
LRU(Least recently used,最近最少使用)算法。該算法的觀點是,最近被訪問的數(shù)據(jù)那么它將來訪問的概率就大,緩存滿的時候,優(yōu)先淘汰最無人問津者。
算法實現(xiàn)思路:基于一個雙鏈表的數(shù)據(jù)結構,在沒有滿員的情況下,新來的 k-v 放在鏈表的頭部,以后每次獲取緩存中的 k-v 時就將該k-v移到最前面,緩存滿的時候優(yōu)先淘汰末尾的。
雙向鏈表的特點,具有頭尾指針,每個節(jié)點都有 prev(前驅(qū)) 和 next(后繼) 指針分別指向他的前一個和后一個節(jié)點。
關鍵點:在雙鏈表的插入過程中要注意順序問題,一定是在保持鏈表不斷的情況下先處理指針,最后才將原頭指針指向新插入的元素,在代碼的實現(xiàn)中請注意看我在注釋中說明的順序注意點!
class LruCache {
constructor(limit) {
this.limit = limit || 10
//head 指針指向表頭元素,即為最常用的元素
this.head = this.tail = undefined
this.map = {}
this.size = 0
}
get(key, IfreturnNode) {
let node = this.map[key]
// 如果查找不到含有`key`這個屬性的緩存對象
if (node === undefined) return
// 如果查找到的緩存對象已經(jīng)是 tail (最近使用過的)
if (node === this.head) { //判斷該節(jié)點是不是是第一個節(jié)點
// 是的話,皆大歡喜,不用移動元素,直接返回
return returnnode ?
node :
node.value
}
// 不是頭結點,鐵定要移動元素了
if (node.prev) { //首先要判斷該節(jié)點是不是有前驅(qū)
if (node === this.tail) { //有前驅(qū),若是尾節(jié)點的話多一步,讓尾指針指向當前節(jié)點的前驅(qū)
this.tail = node.prev
}
//把當前節(jié)點的后繼交接給當前節(jié)點的前驅(qū)去指向。
node.prev.next = node.next
}
if (node.next) { //判斷該節(jié)點是不是有后繼
//有后繼的話直接讓后繼的前驅(qū)指向當前節(jié)點的前驅(qū)
node.next.prev = node.prev
//整個一個過程就是把當前節(jié)點拿出來,并且保證鏈表不斷,下面開始移動當前節(jié)點了
}
node.prev = undefined //移動到最前面,所以沒了前驅(qū)
node.next = this.head //注意!??! 這里要先把之前的排頭給接到手!?。?!讓當前節(jié)點的后繼指向原排頭
if (this.head) {
this.head.prev = node //讓之前的排頭的前驅(qū)指向現(xiàn)在的節(jié)點
}
this.head = node //完成了交接,才能執(zhí)行此步!不然就找不到之前的排頭啦!
return IfreturnNode ?
node :
node.value
}
set(key, value) {
// 之前的算法可以直接存k-v但是現(xiàn)在要把簡單的 k-v 封裝成一個滿足雙鏈表的節(jié)點
//1.查看是否已經(jīng)有了該節(jié)點
let node = this.get(key, true)
if (!node) {
if (this.size === this.limit) { //判斷緩存是否達到上限
//達到了,要刪最后一個節(jié)點了。
if (this.tail) {
this.tail = this.tail.prev
this.tail.prev.next = undefined
//平滑斷鏈之后,銷毀當前節(jié)點
this.tail.prev = this.tail.next = undefined
this.map[this.tail.key] = undefined
//當前緩存內(nèi)存釋放一個槽位
this.size--
}
node = {
key: key
}
this.map[key] = node
if(this.head){//判斷緩存里面是不是有節(jié)點
this.head.prev = node
node.next = this.head
}else{
//緩存里沒有值,皆大歡喜,直接讓head指向新節(jié)點就行了
this.head = node
this.tail = node
}
this.size++//減少一個緩存槽位
}
}
//節(jié)點存不存在都要給他重新賦值啊
node.value = value
}
}
module.exports = LruCache
具體的思路就是如果所要get的節(jié)點不是頭結點(即已經(jīng)是最近使用的節(jié)點了,不需要移動節(jié)點位置)要先進行平滑的斷鏈操作,處理好指針指向的關系,拿出需要移動到最前面的節(jié)點,進行鏈表的插入操作。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
JavaScript實現(xiàn)獲得所有兄弟節(jié)點的方法
這篇文章主要介紹了JavaScript實現(xiàn)獲得所有兄弟節(jié)點的方法,實例分析了javascript節(jié)點遍歷的相關技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-07-07
JavaScript如何使用dhtmlXTreeObject的loadJSONObject繪制目錄樹
這篇文章主要介紹了JavaScript如何使用dhtmlXTreeObject的loadJSONObject繪制目錄樹,需要引入dhtmlXTreeObject的css和js文件,這里還需要注意js的引用順序,本文給大家介紹的非常詳細,需要的的朋友參考下吧2023-11-11

