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个。

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

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

复制代码 代码如下:

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

    

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

时间: 2024-09-20 05:52:41

JavaScript版的TwoQueues缓存模型_基础知识的相关文章

JavaScript高级程序设计(第3版)学习笔记 概述_基础知识

在JavaScript面世之初,没有人会想到它会被应用的如此广泛,也远比一般人想象中的要复杂强大的多,在我自己学习的过程中,曾经有过多次震撼,只是常常没有过多久,很多美轮美奂的用法就又模糊起来,希望通过对JavaScript高级程序设计(第3版)的专题学习笔记,能够较为系统的将基础知识梳理一次,也能够将自己平常学习与工作过程中遇到的一些美妙用法记录下来,便于自己再次学习,当然,也希望可以给有需要的朋友们一些力所能及的帮助. 相关术语 先简要说一下和JavaScript相关的一些背景术语,就不详细

在javascript中对于DOM的加强_基础知识

一.DOM DOM: DOM= Document Object Model,文档对象模型,DOM可以以一种独立于平台和语言的方式访问和修改一个文档的内容和结构.换句话说,这是表示和处理一个HTML或XML文档的常用方法.有一点很重要,DOM的设计是以对象管理组织(OMG)的规约为基础的,因此可以用于任何编程语言.D:文档 – html 文档 或 xml 文档O:对象 – document 对象的属性和方法M:模型DOM 是针对xml(html)的基于树的API.DOM树:节点(node)的层次.

Javascript中的数据类型之旅_基础知识

虽然Javascript是弱类型语言,但是,它也有自己的几种数据类型,分别是:Number.String.Boolean.Object.Udefined.Null.其中,Object属于复杂数据类型,Object   由无序的键值对组成.其余几种都属于简单数据类型.注意:变量类型首字母大写,而变量值首字母是小写的. JavaScript不支持自定义类型,所以JavaScript中的所有值都属于这六种类型之一. 根据ECMAScript 5.1的规范,javascript中共有六种数据类型,分别为

JavaScript 表单处理实现代码_基础知识

一 表单介绍 在HTML中,表单是由<form>元素来表示的,而在JavaScript中,表单对应的则是HTMLFormElement类型; // HTMLFormElement继承了HTMLElement;因此它拥有HTML元素具有的默认属性,别且还独有自己的属性和方法;HTMLFormElement属性和方法属性或方法 说明 acceptCharset 服务器能够处理的字符集; action 接受请求的URL; elements 表单中所有控件的集合; enctype 请求的编码类型; l

JavaScript DOM操作表格及样式_基础知识

一 操作表格 <table>标签是HTML中结构最为复杂的一个,我们可以通过DOM来创建生成它,或者HTMLDOM来操作它; // 使用DOM来创建表格; var table = document.createElement('table'); table.border = 1; table.width = 300; var caption = document.createElement('caption'); table.appendChild(caption); caption.appe

javascript中var的重要性分析_基础知识

本文实例分析了javascript中var的重要性.分享给大家供大家参考.具体分析如下: javascript 的 var 作用是声明变量. 一般情况下不写都不会出错,但有些情况如果不写,会有不同的结果.先看下面的示例: <div id="a"></div> <script type="text/javascript"> a = 1; alert(a); </script> 上面这个例子在FF Chrome执行不会有问

简介JavaScript中toTimeString()方法的使用_基础知识

 该方法返回一个Date对象在人类可读的形式时间部分.语法 Date.toTimeString() 下面是参数的详细信息:     NA 返回值: 返回Date对象的人类可读形式的时间部分.例子: <html> <head> <title>JavaScript toTimeString Method</title> </head> <body> <script type="text/javascript"&g

简述JavaScript中正则表达式的使用方法_基础知识

 正则表达式是一个对象,它描述了字符模式. JavaScript的RegExp类表示正则表达式和字符串和正则表达式定义,使用正则表达式来进行强大的模式匹配和搜索和替换文本功能的方法.语法: 正则表达式可以用RegExp( ) 构造这样的定义: var pattern = new RegExp(pattern, attributes); or simply var pattern = /pattern/attributes; 这里是参数的说明:     pattern: 一个字符串,指定正则表达式

javascript的 {} 语句块详解_基础知识

今日学习解析json字符串,用到了一个eval()方法,解析字符串的时候为什么需要加上括号呢?摸不着头脑.原来javascript中{}语句块具有二义性,不加括号会出错,理解这种二义性对我们理解javascript代码有极大帮助. 一.{}语句块的两个含义 表示语句块 a. 在javascript中可以使用{}来括起代码,在编辑器中方便管理代码.因为javascript并没有块级作用域,所以这种写法是无害的. { //some code... } b. 在javascript中 ,条件判断语句,