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 相关文章推荐
iframe 自适应高度[在IE6 IE7 FF下测试通过]
Apr 13 Javascript
javascript 最常用的10个自定义函数[推荐]
Dec 26 Javascript
Javascript的构造函数和constructor属性
Jan 09 Javascript
jQuery下通过replace字符串替换实现大小图片切换
May 22 Javascript
jQuery实现百叶窗焦点图动画效果代码分享(附源码下载)
Mar 14 Javascript
浅谈JavaScript异步编程
Jan 20 Javascript
jQuery实现表格奇偶行显示不同背景色 就这么简单
Mar 13 Javascript
详解基于Vue-cli搭建的项目如何和后台交互
Jun 29 Javascript
webpack 开发和生产并行设置的方法
Nov 08 Javascript
js JSON.stringify()基础详解
Jun 19 Javascript
layer实现弹出层自动调节位置
Sep 05 Javascript
在layui.use 中自定义 function 的正确方法
Sep 16 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
一个目录遍历函数
2006/10/09 PHP
第二章 PHP入门基础之php代码写法
2011/12/30 PHP
php中通过curl smtp发送邮件
2012/06/05 PHP
thinkphp缓存技术详解
2014/12/09 PHP
ThinkPHP整合datatables实现服务端分页的示例代码
2018/02/10 PHP
jQuery调用AJAX时Get和post公用的乱码解决方法实例说明
2013/06/04 Javascript
js获取多个tagname的节点数组
2013/09/22 Javascript
上传图片预览JS脚本 Input file图片预览的实现示例
2014/10/23 Javascript
JavaScript中的console.assert()函数介绍
2014/12/29 Javascript
jQuery解决input超多的表单提交
2015/08/10 Javascript
JavaScript中日期的相关操作方法总结
2015/10/24 Javascript
jQuery Easyui Tabs扩展根据自定义属性打开页签
2016/08/15 Javascript
ECMAScript6轮播图实践知识总结
2016/08/17 Javascript
AngularJS过滤器filter用法实例分析
2016/11/04 Javascript
基于滚动条位置判断的简单实例
2017/12/14 Javascript
Angular 开发学习之Angular CLI的安装使用
2017/12/31 Javascript
浅谈React高阶组件
2018/03/28 Javascript
vue使用一些外部插件及样式的配置代码
2019/11/18 Javascript
Openlayers绘制聚合标注
2020/09/28 Javascript
Python实现批量下载文件
2015/05/17 Python
在Apache服务器上同时运行多个Django程序的方法
2015/07/22 Python
浅谈Python用QQ邮箱发送邮件时授权码的问题
2018/01/29 Python
基于numpy.random.randn()与rand()的区别详解
2018/04/17 Python
详解Python3.6安装psutil模块和功能简介
2018/05/30 Python
python 字典 按key值大小 倒序取值的实例
2018/07/06 Python
python3在同一行内输入n个数并用列表保存的例子
2019/07/20 Python
python自动分箱,计算woe,iv的实例代码
2019/11/22 Python
详解python 条件语句和while循环的实例代码
2020/12/28 Python
在html5的Canvas上绘制椭圆的几种方法总结
2013/01/07 HTML / CSS
Stuart Weitzman美国官网:美国奢华鞋履品牌
2016/08/18 全球购物
Halston Heritage官网:简洁的日装,稍显奢华的晚装
2018/11/20 全球购物
戴森香港官方网站:Dyson香港
2021/02/11 全球购物
如何提高JDBC的性能
2013/04/30 面试题
专业技术人员年度考核评语
2014/12/31 职场文书
送给小学生的暑假礼物!小学生必背99首古诗
2019/07/02 职场文书
java中用float时,数字后面加f,这样是为什么你知道吗
2021/09/04 Java/Android