JavaScript版的TwoQueues緩存模型
本文所指TwoQueues緩存模型,是說(shuō)數(shù)據(jù)在內(nèi)存中的緩存模型。
無(wú)論何種語(yǔ)言,都可能需要把一部分?jǐn)?shù)據(jù)放在內(nèi)存中,避免重復(fù)運(yùn)算、讀取。最常見(jiàn)的場(chǎng)景就是JQuery選擇器,有些Dom元素的選取是非常耗時(shí)的,我們希望能把這些數(shù)據(jù)緩存起來(lái),不必每次調(diào)用都去重新遍歷Dom樹(shù)。
存就存吧,但總得有個(gè)量吧!總不能把所有的歷史數(shù)據(jù)都放在內(nèi)存中,畢竟目前內(nèi)存的容量還是相當(dāng)可憐的,就算內(nèi)存夠大,理論上每個(gè)線程分配的內(nèi)存也是有限制的。
那么問(wèn)題來(lái)了,如何才能高效的把真正有用的數(shù)據(jù)緩存起來(lái)呢?這就涉及到淘汰算法,需要把垃圾數(shù)據(jù)淘汰掉,才能保住有用的數(shù)據(jù)。
比較常用的思路有以下幾種:
FIFO:就是一個(gè)先進(jìn)先出的隊(duì)列,最先緩存的數(shù)據(jù),最早被淘汰,著名的JQuery框架內(nèi)部就是用的這種模型。
LRU:雙鏈表結(jié)構(gòu),每次有新數(shù)據(jù)存入,直接放在鏈表頭;每次被訪問(wèn)的數(shù)據(jù),也轉(zhuǎn)移到鏈表頭,這樣一來(lái),鏈表尾部的數(shù)據(jù)即是最近沒(méi)被使用過(guò)的,淘汰之。
TwoQueues:FIFO+ LRU,F(xiàn)IFO主要存放初次存入的數(shù)據(jù),LRU中存放至少使用過(guò)兩次的熱點(diǎn)數(shù)據(jù),此算法命中率高,適應(yīng)性強(qiáng),復(fù)雜度低。
其他淘汰算法還有很多很多,但實(shí)際用的比較多的也就這兩種。因?yàn)樗麄儽旧硭惴ú粡?fù)雜,容易實(shí)現(xiàn),執(zhí)行效率高,緩存的命中率在大多數(shù)場(chǎng)合也還可以接受。畢竟緩存算法也是需要消耗CPU的,如果太過(guò)復(fù)雜,雖然命中率有所提高,但得不償失。試想一下,如果從緩存中取數(shù)據(jù),比從原始位置取還消耗時(shí)間,要緩存何用?
具體理論就不多說(shuō)了,網(wǎng)上有的是,我也不怎么懂,今天給大家分享的是JavaScript版的TwoQueues緩存模型。
還是先說(shuō)說(shuō)使用方法,很簡(jiǎn)單。
基本使用方法如下:
[/code]
var tq = initTwoQueues(10);
tq.set("key", "value");
tq.get("key");
[/code]
初始化的時(shí)候,指定一下緩存容量即可。需要注意的是,由于內(nèi)部采用FIFO+LRU實(shí)現(xiàn),所以實(shí)際容量是指定容量的兩倍,上例指定的是10個(gè)(鍵值對(duì)),實(shí)際上可以存放20個(gè)。
容量大小需要根據(jù)實(shí)際應(yīng)用場(chǎng)景而定,太小命中率低,太大效率低,物極必反,需要自己衡量。
在開(kāi)發(fā)過(guò)程中,為了審查緩存效果如何,可以將緩存池初始化成開(kāi)發(fā)版:
var tq = initTwoQueues(10, true);
tq.hitRatio();
就是在后邊加一個(gè)參數(shù),直接true就可以了。這樣初始化的緩存池,會(huì)自動(dòng)統(tǒng)計(jì)命中率,可以通過(guò)hitRatio方法獲取命中率。如果不加這個(gè)參數(shù),hitRatio方法獲取的命中率永遠(yuǎn)為0。
統(tǒng)計(jì)命中率肯定要消耗資源,所以生產(chǎn)環(huán)境下不建議開(kāi)啟。
是時(shí)候分享代碼了:
(function(exports){
/**
* 繼承用的純凈類
* @constructor
*/
function Fn(){}
Fn.prototype = Elimination.prototype;
/**
* 基于鏈表的緩存淘汰算法父類
* @param maxLength 緩存容量
* @constructor
*/
function Elimination(maxLength){
this.container = {};
this.length = 0;
this.maxLength = maxLength || 30;
this.linkHead = this.buildNode("", "");
this.linkHead.head = true;
this.linkTail = this.buildNode("", "");
this.linkTail.tail = true;
this.linkHead.next = this.linkTail;
this.linkTail.prev = this.linkHead;
}
Elimination.prototype.get = function(key){
throw new Error("This method must be override!");
};
Elimination.prototype.set = function(key, value){
throw new Error("This method must be override!");
};
/**
* 創(chuàng)建鏈表中的節(jié)點(diǎn)
* @param data 節(jié)點(diǎn)包含的數(shù)據(jù),即緩存數(shù)據(jù)值
* @param key 節(jié)點(diǎn)的唯一標(biāo)示符,即緩存的鍵
* @returns {{}}
*/
Elimination.prototype.buildNode = function(data, key){
var node = {};
node.data = data;
node.key = key;
node.use = 0;
return node;
};
/**
* 從鏈表頭彈出一個(gè)節(jié)點(diǎn)
* @returns {*}
*/
Elimination.prototype.shift = function(){
var node = null;
if(!this.linkHead.next.tail){
node = this.linkHead.next;
this.linkHead.next = node.next;
node.next.prev = this.linkHead;
delete this.container[node.key];
this.length--;
}
return node;
};
/**
* 從鏈表頭插入一個(gè)節(jié)點(diǎn)
* @param node 節(jié)點(diǎn)對(duì)象
* @returns {*}
*/
Elimination.prototype.unshift = function(node){
node.next = this.linkHead.next;
this.linkHead.next.prev = node;
this.linkHead.next = node;
node.prev = this.linkHead;
this.container[node.key] = node;
this.length++;
return node;
};
/**
* 從鏈表尾插入一個(gè)節(jié)點(diǎn)
* @param node 節(jié)點(diǎn)對(duì)象
* @returns {*}
*/
Elimination.prototype.append = function(node){
this.linkTail.prev.next = node;
node.prev = this.linkTail.prev;
node.next = this.linkTail;
this.linkTail.prev = node;
this.container[node.key] = node;
this.length++;
return node;
};
/**
* 從鏈表尾彈出一個(gè)節(jié)點(diǎn)
* @returns {*}
*/
Elimination.prototype.pop = function(){
var node = null;
if(!this.linkTail.prev.head){
node = this.linkTail.prev;
node.prev.next = this.linkTail;
this.linkTail.prev = node.prev;
delete this.container[node.key];
this.length--;
}
return node;
};
/**
* 從鏈表中移除指定節(jié)點(diǎn)
* @param node 節(jié)點(diǎn)對(duì)象
* @returns {*}
*/
Elimination.prototype.remove = function(node){
node.prev.next = node.next;
node.next.prev = node.prev;
delete this.container[node.key];
this.length--;
return node;
};
/**
* 節(jié)點(diǎn)被訪問(wèn)需要做的處理,具體是把該節(jié)點(diǎn)移動(dòng)到鏈表頭
* @param node
*/
Elimination.prototype.use = function(node){
this.remove(node);
this.unshift(node);
};
/**
* LRU緩存淘汰算法實(shí)現(xiàn)
* @constructor
*/
function LRU(){
Elimination.apply(this, arguments);
}
LRU.prototype = new Fn();
LRU.prototype.get = function(key){
var node = undefined;
node = this.container[key];
if(node){
this.use(node);
}
return node;
};
LRU.prototype.set = function(key, value){
var node = this.buildNode(value, key);
if(this.length === this.maxLength){
this.pop();
}
this.unshift(node);
};
/**
* FIFO緩存淘汰算法實(shí)現(xiàn)
* @constructor
*/
function FIFO(){
Elimination.apply(this, arguments);
}
FIFO.prototype = new Fn();
FIFO.prototype.get = function(key){
var node = undefined;
node = this.container[key];
return node;
};
FIFO.prototype.set = function(key, value){
var node = this.buildNode(value, key);
if(this.length === this.maxLength){
this.shift();
}
this.append(node);
};
/**
* LRU、FIFO算法封裝,成為新的twoqueues緩存淘汰算法
* @param maxLength
* @constructor
*/
function Agent(maxLength){
this.getCount = 0;
this.hitCount = 0;
this.lir = new FIFO(maxLength);
this.hir = new LRU(maxLength);
}
Agent.prototype.get = function(key){
var node = undefined;
node = this.lir.get(key);
if(node){
node.use++;
if(node.use >= 2){
this.lir.remove(node);
this.hir.set(node.key, node.data);
}
}else{
node = this.hir.get(key);
}
return node;
};
Agent.prototype.getx = function(key){
var node = undefined;
this.getCount++;
node = this.get(key);
if(node){
this.hitCount++;
}
return node;
};
Agent.prototype.set = function(key, value){
var node = null;
node = this.lir.container[key] || this.hir.container[key];
if(node){
node.data = value;
}else{
this.lir.set(key, value);
}
};
/**
* 獲取命中率
* @returns {*}
*/
Agent.prototype.hitRatio = function(){
var ret = this.getCount;
if(ret){
ret = this.hitCount / this.getCount;
}
return ret;
};
/**
* 對(duì)外接口
* @param maxLength 緩存容量
* @param dev 是否為開(kāi)發(fā)環(huán)境,開(kāi)發(fā)環(huán)境會(huì)統(tǒng)計(jì)命中率,反之不會(huì)
* @returns {{get, set: Function, hitRatio: Function}}
*/
exports.initTwoQueues = function(maxLength, dev){
var api = new Agent(maxLength);
return {
get: (function(){
if(dev){
return function(key){
var ret = api.getx(key);
return ret && ret.data;
};
}else{
return function(key){
var ret = api.get(key);
return ret && ret.data;
};
}
}()),
set: function(){
api.set.apply(api, arguments);
},
hitRatio: function(){
return api.hitRatio.apply(api, arguments);
}
};
};
}(this));
最后,再次提醒,緩存算法需要和實(shí)際應(yīng)用場(chǎng)景相結(jié)合,沒(méi)有萬(wàn)能算法,合適的才是最好的!
相關(guān)文章
分析Node.js connect ECONNREFUSED錯(cuò)誤
最近在準(zhǔn)備Angularjs +node.js demo的時(shí)候在我的mac開(kāi)發(fā)中 遇見(jiàn)此錯(cuò)誤2013-04-04辨析JavaScript中的Undefined類型與null類型
Undefined與null都是js中的基本數(shù)據(jù)類型,然而正如它們的名字那樣,未初始化和空并不相同,下面我們就來(lái)詳細(xì)辨析JavaScript中的Undefined類型與null類型:2016-05-05窗口沒(méi)有提示自動(dòng)關(guān)閉的js代碼
窗口沒(méi)有提示自動(dòng)關(guān)閉的js代碼...2007-03-03javascript中的location用法簡(jiǎn)單介紹
javascript中的location用法簡(jiǎn)單介紹...2007-03-03