この記事で言及されている TwoQueues キャッシュ モデルは、メモリ内のデータのキャッシュ モデルを指します。
どの言語であっても、操作や読み取りの繰り返しを避けるために、メモリにデータを置く必要がある場合があります。最も一般的なシナリオは、一部の Dom 要素の選択に非常に時間がかかるもので、呼び出されるたびに Dom ツリーを再走査する必要がなく、このデータをキャッシュしたいと考えています。
貯めるだけでも、量はあるはず!すべての履歴データをメモリに置くことは不可能です。メモリが十分に大きいとしても、理論的には各スレッドに割り当てられるメモリ容量は限られています。
そこで問題は、本当に有用なデータを効率的にキャッシュするにはどうすればよいでしょうか?これには、有用なデータを保持するためにジャンク データを除去する必要がある除去アルゴリズムが含まれます。
より一般的に使用されるアイデアは次のとおりです:
FIFO: 最初にキャッシュされたデータが最初に削除されるキューです。このモデルは、有名な JQuery フレームワーク内で使用されます。
LRU: 二重リンク リスト構造。新しいデータが保存されるたびに、そのデータはリンク リストの先頭に直接配置され、データにアクセスされるたびにリンク リストの先頭にも転送されます。このように、リンクリストの最後にあるデータが最新のもので、使用されていないものは削除されます。
TwoQueues: FIFO LRU、FIFO は主に初めて保存されたデータを保存し、LRU は少なくとも 2 回使用されたホットスポット データを保存します。このアルゴリズムは、高いヒット率、強力な適応性、低複雑性を備えています。
他にも多くの消去アルゴリズムがありますが、これらの 2 つが最も一般的に使用されます。その理由は、アルゴリズムが複雑ではなく、実装が簡単で、実行効率が高く、キャッシュ ヒット率がほとんどの状況で許容できるものであるためです。結局のところ、キャッシュアルゴリズムも複雑すぎると、ヒット率は向上しますが、利益は損失に見合いません。想像してみてください。キャッシュからデータを取得する方が、元の場所からデータを取得するよりも時間がかかるとしたら、キャッシュは何の役に立つでしょうか?
インターネット上には多くの理論が存在しますが、私が今日共有したいのは、TwoQueues キャッシュ モデルの JavaScript バージョンです。
まず使い方を説明します。とても簡単です。
基本的な使用方法は次のとおりです。
[/コード]
var tq = initTwoQueues(10);
tq.set("キー", "値");
tq.get("キー");
[/コード]
初期化の際はキャッシュ容量を指定するだけです。 FIFO LRU の内部実装により、実際の容量は指定された容量の 2 倍になることに注意してください。上記の例では 10 (キーと値のペア) が指定されていますが、実際には 20 個保存できます。
容量サイズは、実際のアプリケーションのシナリオに応じて決定する必要があります。小さすぎるとヒット率が低くなり、大きすぎると効率が低くなります。
開発プロセス中に、キャッシュの効果を確認するために、キャッシュ プールを開発バージョンに初期化できます。
var tq = initTwoQueues(10, true);
tq.hitRatio();
最後にパラメータを追加して、それを true にするだけです。この方法で初期化されたキャッシュ プールは自動的にヒット率をカウントし、ヒット率は hitRatio メソッドで取得できます。このパラメータを追加しない場合、hitRatio メソッドで取得されるヒット率は常に 0 になります。
統計的ヒット率は確実にリソースを消費するため、運用環境でこれを有効にすることはお勧めできません。
コードを共有しましょう:
(関数(エクスポート){
/**
* 継承用の純粋なクラス
* @コンストラクター
*/
関数 Fn(){}
Fn.prototype = Elimination.prototype;
/**
* * リンクリストベースのキャッシュ削除アルゴリズムの親クラス
* @param maxLength キャッシュ容量
* @コンストラクター
*/
関数 Elimination(maxLength){
this.container = {};
this.length = 0;
this.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("このメソッドはオーバーライドする必要があります!");
};
Elimination.prototype.set = function(key, value){
throw new Error("このメソッドはオーバーライドする必要があります!");
};
/**
*リンクリストにノードを作成
* @param data ノードに含まれるデータ、つまりキャッシュされたデータ値
* @param key ノードの一意の識別子、つまりキャッシュされたキー
* @returns {{}}
*/
Elimination.prototype.buildNode = function(data, key){
var ノード = {};
ノード.データ = データ;
ノード.キー = キー;
ノード.use = 0;
リターンノード;
};
/**
* リンクリストの先頭からノードをポップします
* @returns {*}
*/
Elimination.prototype.shift = function(){
var ノード = null;
if(!this.linkHead.next.tail){
ノード = this.linkHead.next;
this.linkHead.next = ノード.ネクスト;
node.next.prev = this.linkHead;
this.container[node.key] を削除します;
this.length--;
}
リターンノード;
};
/**
* リンクリストの先頭からノードを挿入
* @param ノード ノード オブジェクト
* @returns {*}
*/
Elimination.prototype.unshift = function(node){
node.next = this.linkHead.next;
this.linkHead.next.prev = ノード;
this.linkHead.next = ノード;
node.prev = this.linkHead;
this.container[node.key] = ノード;
this.length ;
リターンノード;
};
/**
* 从链表尾插入一节点
* @param ノード节点对象
* @returns {*}
*/
Elimination.prototype.append = function(node){
this.linkTail.prev.next = ノード;
node.prev = this.linkTail.prev;
node.next = this.linkTail;
this.linkTail.prev = ノード;
this.container[node.key] = ノード;
this.length ;
リターンノード;
};
/**
* リンクリストの末尾からノードをポップします
* @returns {*}
*/
Elimination.prototype.pop = function(){
var ノード = null;
if(!this.linkTail.prev.head){
ノード = this.linkTail.prev;
node.prev.next = this.linkTail;
this.linkTail.prev = node.prev;
this.container[node.key] を削除します;
this.length--;
}
リターンノード;
};
/**
*指定されたノードをリンクリストから削除します
* @param ノード ノード オブジェクト
* @returns {*}
*/
Elimination.prototype.remove = function(node){
ノード.prev.next = ノード.ネクスト;
ノード.next.prev = ノード.prev;
this.container[node.key] を削除します;
this.length--;
リターンノード;
};
/**
* ノードにアクセスしたときに実行する必要がある処理、具体的にはノードをリンクリストの先頭に移動する
* @param ノード
*/
Elimination.prototype.use = function(node){
this.remove(node);
this.unshift(node);
};
/**
* LRUキャッシュ削除アルゴリズムの実装
* @コンストラクター
*/
関数 LRU(){
Elimination.apply(this, argument);
}
LRU.prototype = new Fn();
LRU.prototype.get = function(key){
var ノード = 未定義;
ノード = this.container[キー];
if(ノード){
this.use(ノード);
}
リターンノード;
};
LRU.prototype.set = function(key, value){
var ノード = this.buildNode(値, キー);
if(this.length === this.maxLength){
this.pop();
}
this.unshift(node);
};
/**
* FIFOキャッシュ削除アルゴリズムの実装
* @コンストラクター
*/
関数 FIFO(){
Elimination.apply(this, argument);
}
FIFO.prototype = new Fn();
FIFO.prototype.get = function(key){
var ノード = 未定義;
ノード = this.container[キー];
リターンノード;
};
FIFO.prototype.set = function(key, value){
var ノード = this.buildNode(値, キー);
if(this.length === this.maxLength){
this.shift();
}
this.append(node);
};
/**
* LRU および FIFO アルゴリズムのカプセル化、新しい 2 つのキューのキャッシュ削除アルゴリズムになります
* @param maxLength
* @コンストラクター
*/
関数 Agent(maxLength){
this.getCount = 0;
this.hitCount = 0;
this.lir = 新しい FIFO(maxLength);
this.hir = 新しい LRU(maxLength);
}
Agent.prototype.get = function(key){
var ノード = 未定義;
ノード = this.lir.get(key);
if(ノード){
ノード.使用 ;
if(node.use >= 2){
this.lir.remove(node);
this.hir.set(node.key, node.data);
}
}その他{
ノード = this.hir.get(key);
}
リターンノード;
};
Agent.prototype.getx = function(key){
var ノード = 未定義;
this.getCount ;
ノード = this.get(key);
if(ノード){
this.hitCount ;
}
リターンノード;
};
Agent.prototype.set = function(key, value){
var ノード = null;
ノード = this.lir.container[キー] || this.hir.container[キー];
if(ノード){
ノード.データ = 値;
}その他{
this.lir.set(キー, 値);
}
};
/**
*命中率を取得
* @returns {*}
*/
Agent.prototype.hitRatio = function(){
var ret = this.getCount;
if(ret){
ret = this.hitCount / this.getCount;
}
return ret;
};
/**
* 对外部インターフェイス
* @param maxLength キャッシュ容量
* @param dev 開発環境かどうかにかかわらず、開発環境はヒット率をカウントしますが、それ以外の場合はヒット率をカウントしません
* @returns {{get, set: 関数, hitRatio: 関数}}
*/
exports.initTwoQueues = function(maxLength, dev){
var api = new Agent(maxLength);
return {
get: (function(){
If(dev){
戻り関数(キー){
var ret = api.getx(key);
return ret && ret.data;
};
}else{
戻り関数(キー){
var ret = api.get(key);
return ret && ret.data;
};
}
}())、
set: function(){
api.set.apply(api, 引数);
},
hitRatio: function(){
return api.hitRatio.apply(api, argument);
}
};
};
}(これ));
最後に、キャッシュ アルゴリズムは実際のアプリケーション シナリオと組み合わせる必要があることをもう一度思い出していただきたいと思います。普遍的なアルゴリズムはなく、適切なアルゴリズムが最適です。