JavaScript版的TwoQueues缓存模型


Posted in Javascript onDecember 29, 2014

本文所指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个。

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

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

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

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

Javascript 相关文章推荐
js对列表中第一个值处理与jsp页面对列表中第一个值处理的区别详解
Nov 05 Javascript
在easyUI开发中,出现jquery.easyui.min.js函数库问题的解决办法
Sep 11 Javascript
Document.body.scrollTop的值总为零的快速解决办法
Jun 09 Javascript
浅谈JavaScript 中有关时间对象的方法
Aug 15 Javascript
angularJs关于指令的一些冷门属性详解
Oct 24 Javascript
Vue组件实例间的直接访问实现代码
Aug 20 Javascript
AngularJS实现的省市二级联动功能示例【可对选项实现增删】
Oct 26 Javascript
elementUI Vue 单个按钮显示和隐藏的变换功能(两种方法)
Sep 04 Javascript
jQuery实现的别踩白块小游戏完整示例
Jan 07 jQuery
vue实现密码显示与隐藏按钮的自定义组件功能
Apr 23 Javascript
微信小程序自定义单项选择器样式
Jul 25 Javascript
layui 对table中的数据进行转义的实例
Sep 12 Javascript
浅谈重写window对象的方法
Dec 29 #Javascript
JavaScript中的console.log()函数详细介绍
Dec 29 #Javascript
深入分析原生JavaScript事件
Dec 29 #Javascript
JavaScript中的alert()函数使用技巧详解
Dec 29 #Javascript
JavaScript实现三阶幻方算法谜题解答
Dec 29 #Javascript
浅谈JavaScript Date日期和时间对象
Dec 29 #Javascript
jQuery中clearQueue()方法用法实例
Dec 29 #Javascript
You might like
PHP 5.3.1 安装包 VC9 VC6不同版本的区别是什么
2010/07/04 PHP
比较时间段一与时间段二是否有交集的php函数
2011/05/31 PHP
php可生成缩略图的文件上传类实例
2014/12/17 PHP
Apache无法自动跳转却显示目录的解决方法
2020/11/30 PHP
yii2控制器Controller Ajax操作示例
2016/07/23 PHP
浅谈PHP表单提交(POST&GET&URL编/解码)
2017/04/03 PHP
PHP排序算法之基数排序(Radix Sort)实例详解
2018/04/21 PHP
php-fpm服务启动脚本的方法
2018/04/27 PHP
RR vs IO BO3 第二场2.13
2021/03/10 DOTA
用 Javascript 验证表单(form)中的单选(radio)值
2009/09/08 Javascript
十个优秀的Ajax/Javascript实例网站收集
2010/03/31 Javascript
AlertBox 弹出层信息提示框效果实现步骤
2010/10/11 Javascript
JQUERY 设置SELECT选中项代码
2014/02/07 Javascript
纯JavaScript实现的分页插件实例
2015/07/14 Javascript
JavaScript数组去重的多种方法(四种)
2017/09/19 Javascript
vue实现将数据存入vuex中以及从vuex中取出数据
2019/11/08 Javascript
原生js实现自定义难度的扫雷游戏
2021/01/22 Javascript
python实现监控windows服务并自动启动服务示例
2014/04/17 Python
Python实现建立SSH连接的方法
2015/06/03 Python
Python内置函数——__import__ 的使用方法
2017/11/24 Python
Python实现的质因式分解算法示例
2018/05/03 Python
浅谈django orm 优化
2018/08/18 Python
通过python实现弹窗广告拦截过程详解
2019/07/10 Python
Python语法之精妙的十个知识点(装B语法)
2020/01/18 Python
Python 实现3种回归模型(Linear Regression,Lasso,Ridge)的示例
2020/10/15 Python
HTML5之SVG 2D入门12—SVG DOM及DOM操作介绍
2013/01/30 HTML / CSS
html5写一个BUI折叠菜单插件的实现方法
2019/09/11 HTML / CSS
Luxplus瑞典:香水和美容护理折扣
2018/01/28 全球购物
伦敦一家西班牙童装精品店:La Coqueta
2018/02/02 全球购物
2014三八妇女节活动总结范文四篇
2014/03/09 职场文书
毕业班联欢会主持词
2014/03/27 职场文书
家长会欢迎标语
2014/06/24 职场文书
欢迎领导检查标语
2014/06/27 职场文书
首都博物馆观后感
2015/06/05 职场文书
人生哲理妙语30条:淡写流年,笑过人生
2019/09/04 职场文书
六年级作文之关于梦
2019/10/22 职场文书