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

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

(0)

相关推荐

  • 一个简单的JavaScript数据缓存系统实现代码

    复制代码 代码如下: var DataCache = function(){ if(!(this instanceof DataCache)){ return new DataCache(); } this.id = 0; this.caches = {}; }; DataCache.prototype = { add : function(val){ val = val || null; key = "dc_" + this.id; this.caches[key] = val; r

  • javascript的动态加载、缓存、更新以及复用(一)

    使用范围: OA.MIS.ERP等信息管理类的项目,暂时不考虑网站. 遇到的问题: 完成一个项目,往往需要引用很多js文件,比如jQuery.js.easyUI等.还有自己写的一些列js文件,那么这些文件如何方便的加载,如果文件有变化如何才能让客户端及时更新缓存?如果能够提高点运行效率,那就更好了. 目标: 1.  可以方便的引用js文件. 2.  尽量使用各种缓存,避免频繁从服务器读取文件. 3.  如果js文件有更新或者增加.减少几个减少js文件,需要客户端能够自动.立刻更新. 4.  Js

  • Javascript Memoization 缓存函数使用说明

    举个例子 复制代码 代码如下: var flower= function(){ var t=0,i=0; for(;i<5000000;i++){ t++; } return t; } flower 返回t的值 假设这个函数需要花费 2-3秒 . 通过 Memoization 函数,再次查找相同的值时,直接获取事先缓存好的 value,立刻返回; Memoization 函数 复制代码 代码如下: var Memoize = function(fn, cache, refetch, obj){

  • javascript下利用数组缓存正则表达式的实现方法

    如果能用字面量创建正则就最好不过,显然有时我们不得不使用new RegExp()这种大消耗的创建方法,比如语法高亮与排版就大量用到正则表达式,要用到的patten越多,需要的时间就越长,火狐好像是12秒就发出警告,IE就直接假死.这时我们就需要利用组存大法要提高我们程序的性能了. 通常摆在我们眼前的如下两种选择来作为我们的容器,数组或对象.我这里选择前者,前者更轻量一点.下面我们就hasClass函数作性能改进. 原来的写法: 复制代码 代码如下: var hasClass = function

  • javascript客户端解决方案 缓存提供程序

    相信每一个开发者都知道缓存的重要性.从头至尾有缓存的后台(memcached,xcache等.) 来减轻db的压力.对内容分发网络(CDN)缓存中希望你的浏览器缓存那些不止一次的加载资源.当然, 有客户端缓存,所以你不要重复昂贵的操作(即使是算法或大量的运算). 这是介绍的是一个不错的javascript的方面的客户端解决方案,可选配支持HTML5本地存储器. Starting Simple 复制代码 代码如下: function CacheProvider() { // values will

  • JavaScript中的常见问题解决方法(乱码,IE缓存,代理)

    解决AJAX中文乱码常用的两种方法 1. 在客户端进行encodeURI(utf-8也可以不做,默认),在服务器端将iso-8859-1编码转为utf-8编码 2.在客户端进行两次encodeURI,在服务器端进行一次转换. 第2种方法能解决问题的原因: 进行两次转换后,在第一次getparameter方法中进行第一次解码,因为解出来的是英文(第一次encode之后的结果),所以不会出问题:第二次使用URLDecoder的decode方法,所以能正常解决这个问题.需要注意的是,在decode方法

  • JavaScript版的TwoQueues缓存模型

    本文所指TwoQueues缓存模型,是说数据在内存中的缓存模型. 无论何种语言,都可能需要把一部分数据放在内存中,避免重复运算.读取.最常见的场景就是JQuery选择器,有些Dom元素的选取是非常耗时的,我们希望能把这些数据缓存起来,不必每次调用都去重新遍历Dom树. 存就存吧,但总得有个量吧!总不能把所有的历史数据都放在内存中,毕竟目前内存的容量还是相当可怜的,就算内存够大,理论上每个线程分配的内存也是有限制的. 那么问题来了,如何才能高效的把真正有用的数据缓存起来呢?这就涉及到淘汰算法,需要

  • 纯javascript版日历控件

    平时只有下班时间能code,闲来写了个纯javascript版.引用该calendar.js文件,然后给要设置成日历控件的input的id设置成calendar,该input就会变成日历控件. <!doctype html> <html> <head> <meta charset="utf-8"> <title>日历控件</title> <script src="js/calendar.js&quo

  • 琥珀无限级联动菜单-JavaScript版

    琥珀网 - javascript无限级联动菜单 body, td { font-family: 宋体; font-size: 12px; } /*---------------------------------------------------------------------------*\ | Subject: JavaScript三级联动菜单 | | Version: 1.0 | | Author: 琥珀[hopesoft] | | FileName: HPMenu.js | | C

  • JavaScript版代码高亮

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>JavaScript版代码高亮</title><link href=&

  • JavaScript版经典游戏之扫雷游戏完整示例【附demo源码下载】

    本文实例讲述了JavaScript扫雷游戏.分享给大家供大家参考,具体如下: 翻出年初写的游戏贴上来,扫雷相信大家都玩过,先上图: 源码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.or

  • JavaScript版TAB选项卡效果实例

    复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="

  • 仿豆瓣分页原型(Javascript版)

    好久没发过帖子了~~. 因为工作需要,仿豆瓣式写了个分页的样式. 自我感觉,这样的分页前后兼顾,对于用户的体验是蛮好使的. 仿豆瓣分页原型(Javascript版) /* Paginator */ .paginator { font: 14.8px normal Arial, Helvetica, sans-serif; color: #666666; margin-top: 10px; margin-bottom: 5px; line-height: 150%; background-colo

  • JavaScript双向链表实现LRU缓存算法的示例代码

    目录 目标 什么是LRU 简介 硬件支持 寄存器 栈 代码实现 思路 链表节点数据结构 链表数据结构 LRUCache数据结构 完整代码 测试 目标 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构. 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 . void put(int key

  • JavaScript双向链表实现LFU缓存算法

    目录 什么是LFU 描述 解题思路 1.构造节点结构体 2.构造双向链表 3.编写链表头添加节点方法 4.编写删除节点方法 5.构造LRU缓存结构体 6.编写get方法 7.编写put方法 什么是LFU LFU(Least Frequently Used),最近最少使用策略,也就是说在一段时间内,数据被使用频次最少的,优先被淘汰.它是一种用于管理计算机内存的缓存算法,采用LFU算法的最简单方法是为每个加载到缓存的块分配一个计数器.每次引用该块时,计数器将增加一.当缓存达到容量并有一个新的内存块等

  • Kafka Producer中的消息缓存模型图解详解

    目录 前言 什么是消息累加器RecordAccumulator 消息缓存模型 ProducerBatch的内存大小 内存分配 Batch的创建和释放 问题和答案 总结 前言 在阅读本文之前, 希望你可以思考一下下面几个问题, 带着问题去阅读文章会获得更好的效果. 发送消息的时候, 当Broker挂掉了,消息体还能写入到消息缓存中吗? 当消息还存储在缓存中的时候, 假如Producer客户端挂掉了,消息是不是就丢失了? 当最新的ProducerBatch还有空余的内存,但是接下来的一条消息很大,不

随机推荐