JavaScript版的TwoQueues缓存模型

本文所指TwoQueues缓存模型,是说数据在内存中的缓存模型。

无论何种语言,都可能需要把一部分数据放在内存中,避免重复运算、读取。最常见的场景就是JQuery选择器,有些Dom元素的选取是非常耗时的,我们希望能把这些数据缓存起来,不必每次调用都去重新遍历Dom树。

存就存吧,但总得有个量吧!总不能把所有的历史数据都放在内存中,毕竟目前内存的容量还是相当可怜的,就算内存够大,理论上每个线程分配的内存也是有限制的。

那么问题来了,如何才能高效的把真正有用的数据缓存起来呢?这就涉及到淘汰算法,需要把垃圾数据淘汰掉,才能保住有用的数据。

比较常用的思路有以下几种:

FIFO:就是一个先进先出的队列,最先缓存的数据,最早被淘汰,著名的JQuery框架内部就是用的这种模型。

LRU:双链表结构,每次有新数据存入,直接放在链表头;每次被访问的数据,也转移到链表头,这样一来,链表尾部的数据即是最近没被使用过的,淘汰之。

TwoQueues:FIFO+ LRU,FIFO主要存放初次存入的数据,LRU中存放至少使用过两次的热点数据,此算法命中率高,适应性强,复杂度低。

其他淘汰算法还有很多很多,但实际用的比较多的也就这两种。因为他们本身算法不复杂,容易实现,执行效率高,缓存的命中率在大多数场合也还可以接受。毕竟缓存算法也是需要消耗cpu的,如果太过复杂,虽然命中率有所提高,但得不偿失。试想一下,如果从缓存中取数据,比从原始位置取还消耗时间,要缓存何用?

具体理论就不多说了,网上有的是,我也不怎么懂,今天给大家分享的是JavaScript版的TwoQueues缓存模型。

还是先说说使用方法,很简单。

基本使用方法如下:

[/code] var tq = initTwoQueues(10); tq.set("key","value"); tq.get("key"); [/code]

初始化的时候,指定一下缓存容量即可。需要注意的是,由于内部采用FIFO+LRU实现,所以实际容量是指定容量的两倍,上例指定的是10个(键值对),实际上可以存放20个。

容量大小需要根据实际应用场景而定,太小命中率低,太大效率低,物极必反,需要自己衡量。

在开发过程中,为了审查缓存效果如何,可以将缓存池初始化成开发版:

代码如下:

就是在后边加一个参数,直接true就可以了。这样初始化的缓存池,会自动统计命中率,可以通过hitRatio方法获取命中率。如果不加这个参数,hitRatio方法获取的命中率永远为0。 统计命中率肯定要消耗资源,所以生产环境下不建议开启。 是时候分享代码了:

代码如下:
/**

  • 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,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));

最后,再次提醒,缓存算法需要和实际应用场景相结合,没有万能算法,合适的才是最好的!

相关文章

前言 做过web项目开发的人对layer弹层组件肯定不陌生,作为l...
前言 前端表单校验是过滤无效数据、假数据、有毒数据的第一步...
前言 图片上传是web项目常见的需求,我基于之前的博客的代码...
前言 导出Excel文件这个功能,通常都是在后端实现返回前端一...
前言 众所周知,js是单线程的,从上往下,从左往右依次执行,...
前言 项目开发中,我们可能会碰到这样的需求:select标签,禁...