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 相关文章推荐
javascript之Partial Application学习
Jan 10 Javascript
js关闭浏览器窗口及检查浏览器关闭事件
Sep 03 Javascript
JavaScript字符串对象substr方法入门实例(用于截取字符串)
Oct 16 Javascript
基于JS2Image实现圣诞树代码
Dec 24 Javascript
体验jQuery和AngularJS的不同点及AngularJS的迷人之处
Feb 02 Javascript
jQuery ready()和onload的加载耗时分析
Sep 08 Javascript
node.js中的事件处理机制详解
Nov 26 Javascript
vue如何引用其他组件(css和js)
Apr 13 Javascript
jquery-file-upload 文件上传带进度条效果
Nov 21 jQuery
layui 中select下拉change事件失效的解决方法
Sep 20 Javascript
Vue2.0搭建脚手架
Mar 13 Vue.js
微信小程序APP页面的之间的相互传递参数以及自定义组件
Apr 19 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 socket方式提交的post详解
2008/07/19 PHP
php编写一个简单的路由类
2011/04/13 PHP
Linux下实现PHP多进程的方法分享
2012/08/16 PHP
PHP使用DirectoryIterator显示下拉文件列表的方法
2015/03/13 PHP
B/S模式项目中常用的javascript汇总
2013/12/17 Javascript
JavaScript静态类型检查工具FLOW简介
2015/01/06 Javascript
jQuery插件slider实现拖动滑块选取价格范围
2015/04/30 Javascript
为何JS操作的href都是javascript:void(0);呢
2015/11/12 Javascript
简单讲解AngularJS的Routing路由的定义与使用
2016/03/05 Javascript
JS代码随机生成姓名、手机号、身份证号、银行卡号
2016/04/27 Javascript
JavaScript中Array的实用操作技巧分享
2016/09/11 Javascript
TypeScript学习之强制类型的转换
2016/12/27 Javascript
原生javascript实现读写CSS样式的方法详解
2017/02/20 Javascript
vue.js选中动态绑定的radio的指定项
2017/06/02 Javascript
gulp教程_从入门到项目中快速上手使用方法
2017/09/14 Javascript
深入浅析javascript继承体系
2017/10/23 Javascript
vue cli webpack中使用sass的方法
2018/02/24 Javascript
js判断在哪个浏览器打开项目的方法
2020/01/21 Javascript
[56:58]VP vs Optic 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
pandas多级分组实现排序的方法
2018/04/20 Python
Django migrations 默认目录修改的方法教程
2018/09/28 Python
Python time库基本使用方法分析
2019/12/13 Python
python模式 工厂模式原理及实例详解
2020/02/11 Python
什么是Python变量作用域
2020/06/03 Python
Python利用matplotlib绘制折线图的新手教程
2020/11/05 Python
Python3爬虫ChromeDriver的安装实例
2021/02/06 Python
阿迪达斯意大利在线商店:adidas意大利
2016/09/19 全球购物
欧洲领先的技术商店:eibmarkt.com
2019/05/10 全球购物
美国二手复古奢侈品包包购物网站:LXRandCo
2019/06/18 全球购物
建筑施工实习自我鉴定
2013/09/19 职场文书
《窗前的气球》教学反思
2014/04/07 职场文书
《谁的本领大》教后反思
2014/04/25 职场文书
入党积极分子学习党的纲领思想汇报
2014/09/13 职场文书
新学期红领巾广播稿
2014/10/04 职场文书
2015关爱留守儿童工作总结
2014/12/12 职场文书
2015年留守儿童工作总结
2015/05/22 职场文书