JavaScript實現TwoQueues緩衝模型

來源:互聯網
上載者:User

    本文所指TwoQueues緩衝模型,是說資料在記憶體中的緩衝模型。

     無論何種語言,都可能需要把一部分資料放在記憶體中,避免重複運算、讀取。最常見的情境就是JQuery選取器,有些Dom元素的選取是非常耗時的,我們希望能把這些資料緩衝起來,不必每次調用都去重新遍曆Dom樹。

     存就存吧,但總得有個量吧!總不能把所有的曆史資料都放在記憶體中,畢竟目前記憶體的容量還是相當可憐的,就算記憶體夠大,理論上每個線程分配的記憶體也是有限制的。

     那麼問題來了,如何才能高效的把真正有用的資料緩衝起來呢?這就涉及到淘汰演算法,需要把垃圾資料淘汰掉,才能保住有用的資料。

     比較常用的思路有以下幾種:

       

         FIFO:就是一個先進先出的隊列,最先緩衝的資料,最早被淘汰,著名的JQuery架構內部就是用的這種模型。

         LRU:雙鏈表結構,每次有新資料存入,直接放在鏈表頭;每次被訪問的資料,也轉移到鏈表頭,這樣一來,鏈表尾部的資料即是最近沒被使用過的,淘汰之。

         TwoQueues:FIFO+ LRU,FIFO主要存放初次存入的資料,LRU中存放至少使用過兩次的熱點資料,此演算法命中率高,適應性強,複雜度低。

 

     其他淘汰演算法還有很多很多,但實際用的比較多的也就這兩種。因為他們本身演算法不複雜,容易實現,執行效率高,緩衝的命中率在大多數場合也還可以接受。畢竟緩衝演算法也是需要消耗CPU的,如果太過複雜,雖然命中率有所提高,但得不償失。試想一下,如果從緩衝中取資料,比從原始位置取還消耗時間,要緩衝何用?

     具體理論就不多說了,網上有的是,我也不怎麼懂,今天給大家分享的是JavaScript版的TwoQueues緩衝模型。

     還是先說說使用方法,很簡單。

     基本使用方法如下:

 var tq = initTwoQueues(10);
 tq.set("key", "value");
 tq.get("key");

     初始化的時候,指定一下緩衝容量即可。需要注意的是,由於內部採用FIFO+LRU實現,所以實際容量是指定容量的兩倍,上例指定的是10個(索引值對),實際上可以存放20個。

     容量大小需要根據實際應用情境而定,太小命中率低,太大效率低,物極必反,需要自己衡量。

     在開發過程中,為了審查緩衝效果如何,可以將緩衝池初始化成開發版:

 var tq = initTwoQueues(10, true);
 tq.hitRatio();

     就是在後邊加一個參數,直接true就可以了。這樣初始化的緩衝池,會自動統計命中率,可以通過hitRatio方法擷取命中率。如果不加這個參數,hitRatio方法擷取的命中率永遠為0。

     統計命中率肯定要消耗資源,所以生產環境下不建議開啟。

     是時候分享代碼了:

(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!");    };    /**     * 建立鏈表中的節點     * @param data 節點包含的資料,即快取資料值     * @param key 節點的唯一標示符,即緩衝的鍵     * @returns {{}}     */    Elimination.prototype.buildNode = function(data, key){        var node = {};        node.data = data;        node.key = key;        node.use = 0;        return node;    };    /**     * 從鏈表頭彈出一個節點     * @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;    };    /**     * 從鏈表頭插入一個節點     * @param node 節點對象     * @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;    };    /**     * 從鏈表尾插入一個節點     * @param node 節點對象     * @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;    };    /**     * 從鏈表尾彈出一個節點     * @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;    };    /**     * 從鏈表中移除指定節點     * @param node 節點對象     * @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;    };    /**     * 節點被訪問需要做的處理,具體是把該節點移動到鏈表頭     * @param node     */    Elimination.prototype.use = function(node){        this.remove(node);        this.unshift(node);    };    /**     * LRU緩衝淘汰演算法實現     * @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緩衝淘汰演算法實現     * @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;    };    /**     * 對外介面     * @param maxLength 緩衝容量     * @param dev 是否為開發環境,開發環境會統計命中率,反之不會     * @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));

     最後,再次提醒,緩衝演算法需要和實際應用情境相結合,沒有萬能演算法,合適的才是最好的!



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.