本文所指TwoQueues缓存模型,是说数据在内存中的缓存模型。

无论何种语言,都可能需要把一部分数据放在内存中,避免重复运算、读取。最常见的场景就是JQuery选择器,有些Dom元素的选取是非常耗时的,我们希望能把这些数据缓存起来,不必每次调用都去重新遍历Dom树。

存就存吧,但总得有个量吧!总不能把所有的历史数据都放在内存中,毕竟目前内存的容量还是相当可怜的,就算内存够大,理论上每个线程分配的内存也是有限制的。

那么问题来了,如何才能高效的把真正有用的数据缓存起来呢?这就涉及到淘汰算法,需要把垃圾数据淘汰掉,才能保住有用的数据。

比较常用的思路有以下几种:

FIFO:就是一个先进先出的队列,最先缓存的数据,最早被淘汰,著名的JQuery框架内部就是用的这种模型。

LRU:双链表结构,每次有新数据存入,直接放在链表头;每次被访问的数据,也转移到链表头,这样一来,链表尾部的数据即是最近没被使用过的,淘汰之。

TwoQueues:FIFO+ LRU,FIFO主要存放初次存入的数据,LRU中存放至少使用过两次的热点数据,此算法命中率高,适应性强,复杂度低。

其他淘汰算法还有很多很多,但实际用的比较多的也就这两种。因为他们本身算法不复杂,容易实现,执行效率高,缓存的命中率在大多数场合也还可以接受。毕竟缓存算法也是需要消耗CPU的,如果太过复杂,虽然命中率有所提高,但得不偿失。试想一下,如果从缓存中取数据,比从原始位置取还消耗时间,要缓存何用?

具体理论就不多说了,网上有的是,我也不怎么懂,今天给大家分享的是JavaScript版的TwoQueues缓存模型。

还是先说说使用方法,很简单。

基本使用方法如下:

1 var tq = initTwoQueues(10);
2 tq.set("key", "value");
3 tq.get("key");

初始化的时候,指定一下缓存容量即可。需要注意的是,由于内部采用FIFO+LRU实现,所以实际容量是指定容量的两倍,上例指定的是10个(键值对),实际上可以存放20个。

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

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

1 var tq = initTwoQueues(10, true);
2 tq.hitRatio();

就是在后边加一个参数,直接true就可以了。这样初始化的缓存池,会自动统计命中率,可以通过hitRatio方法获取命中率。如果不加这个参数,hitRatio方法获取的命中率永远为0。

统计命中率肯定要消耗资源,所以生产环境下不建议开启。

是时候分享代码了:

  1 (function(exports){
  2
  3     /**
  4      * 继承用的纯净类
  5      * @constructor
  6      */
  7     function Fn(){}
  8     Fn.prototype = Elimination.prototype;
  9
 10     /**
 11      * 基于链表的缓存淘汰算法父类
 12      * @param maxLength 缓存容量
 13      * @constructor
 14      */
 15     function Elimination(maxLength){
 16         this.container = {};
 17         this.length = 0;
 18         this.maxLength = maxLength || 30;
 19         this.linkHead = this.buildNode("", "");
 20         this.linkHead.head = true;
 21         this.linkTail = this.buildNode("", "");
 22         this.linkTail.tail = true;
 23
 24         this.linkHead.next = this.linkTail;
 25         this.linkTail.prev = this.linkHead;
 26     }
 27
 28     Elimination.prototype.get = function(key){
 29         throw new Error("This method must be override!");
 30     };
 31
 32     Elimination.prototype.set = function(key, value){
 33         throw new Error("This method must be override!");
 34     };
 35
 36     /**
 37      * 创建链表中的节点
 38      * @param data 节点包含的数据,即缓存数据值
 39      * @param key 节点的唯一标示符,即缓存的键
 40      * @returns {{}}
 41      */
 42     Elimination.prototype.buildNode = function(data, key){
 43         var node = {};
 44         node.data = data;
 45         node.key = key;
 46         node.use = 0;
 47
 48         return node;
 49     };
 50
 51     /**
 52      * 从链表头弹出一个节点
 53      * @returns {*}
 54      */
 55     Elimination.prototype.shift = function(){
 56         var node = null;
 57         if(!this.linkHead.next.tail){
 58             node = this.linkHead.next;
 59             this.linkHead.next = node.next;
 60             node.next.prev = this.linkHead;
 61
 62             delete this.container[node.key];
 63             this.length--;
 64         }
 65
 66         return node;
 67     };
 68
 69     /**
 70      * 从链表头插入一个节点
 71      * @param node 节点对象
 72      * @returns {*}
 73      */
 74     Elimination.prototype.unshift = function(node){
 75         node.next = this.linkHead.next;
 76         this.linkHead.next.prev = node;
 77
 78         this.linkHead.next = node;
 79         node.prev = this.linkHead;
 80
 81         this.container[node.key] = node;
 82         this.length++;
 83
 84         return node;
 85     };
 86
 87     /**
 88      * 从链表尾插入一个节点
 89      * @param node 节点对象
 90      * @returns {*}
 91      */
 92     Elimination.prototype.append = function(node){
 93
 94         this.linkTail.prev.next = node;
 95         node.prev = this.linkTail.prev;
 96
 97         node.next = this.linkTail;
 98         this.linkTail.prev = node;
 99
100         this.container[node.key] = node;
101         this.length++;
102
103         return node;
104     };
105
106     /**
107      * 从链表尾弹出一个节点
108      * @returns {*}
109      */
110     Elimination.prototype.pop = function(){
111         var node = null;
112
113         if(!this.linkTail.prev.head){
114             node = this.linkTail.prev;
115             node.prev.next = this.linkTail;
116             this.linkTail.prev = node.prev;
117
118             delete this.container[node.key];
119             this.length--;
120         }
121
122         return node;
123     };
124
125     /**
126      * 从链表中移除指定节点
127      * @param node 节点对象
128      * @returns {*}
129      */
130     Elimination.prototype.remove = function(node){
131         node.prev.next = node.next;
132         node.next.prev = node.prev;
133
134         delete this.container[node.key];
135         this.length--;
136
137         return node;
138     };
139
140     /**
141      * 节点被访问需要做的处理,具体是把该节点移动到链表头
142      * @param node
143      */
144     Elimination.prototype.use = function(node){
145         this.remove(node);
146         this.unshift(node);
147     };
148
149
150     /**
151      * LRU缓存淘汰算法实现
152      * @constructor
153      */
154     function LRU(){
155         Elimination.apply(this, arguments);
156     }
157     LRU.prototype = new Fn();
158
159     LRU.prototype.get = function(key){
160         var node = undefined;
161
162         node = this.container[key];
163
164         if(node){
165             this.use(node);
166         }
167
168         return node;
169     };
170
171     LRU.prototype.set = function(key, value){
172         var node = this.buildNode(value, key);
173
174         if(this.length === this.maxLength){
175             this.pop();
176         }
177
178         this.unshift(node);
179     };
180
181
182     /**
183      * FIFO缓存淘汰算法实现
184      * @constructor
185      */
186     function FIFO(){
187         Elimination.apply(this, arguments);
188     }
189     FIFO.prototype = new Fn();
190
191     FIFO.prototype.get = function(key){
192         var node = undefined;
193
194         node = this.container[key];
195
196         return node;
197     };
198
199     FIFO.prototype.set = function(key, value){
200         var node = this.buildNode(value, key);
201
202         if(this.length === this.maxLength){
203             this.shift();
204         }
205
206         this.append(node);
207     };
208
209
210     /**
211      * LRU、FIFO算法封装,成为新的twoqueues缓存淘汰算法
212      * @param maxLength
213      * @constructor
214      */
215     function Agent(maxLength){
216         this.getCount = 0;
217         this.hitCount = 0;
218         this.lir = new FIFO(maxLength);
219         this.hir = new LRU(maxLength);
220     }
221
222     Agent.prototype.get = function(key){
223         var node = undefined;
224
225         node = this.lir.get(key);
226
227         if(node){
228             node.use++;
229             if(node.use >= 2){
230                 this.lir.remove(node);
231                 this.hir.set(node.key, node.data);
232             }
233         }else{
234             node = this.hir.get(key);
235         }
236
237         return node;
238     };
239
240     Agent.prototype.getx = function(key){
241         var node = undefined;
242
243         this.getCount++;
244
245         node = this.get(key);
246
247         if(node){
248             this.hitCount++;
249         }
250
251         return node;
252     };
253
254     Agent.prototype.set = function(key, value){
255         var node = null;
256
257         node = this.lir.container[key] || this.hir.container[key];
258
259         if(node){
260             node.data = value;
261         }else{
262             this.lir.set(key, value);
263         }
264     };
265
266     /**
267      * 获取命中率
268      * @returns {*}
269      */
270     Agent.prototype.hitRatio = function(){
271         var ret = this.getCount;
272
273         if(ret){
274             ret = this.hitCount / this.getCount;
275         }
276
277         return ret;
278     };
279
280     /**
281      * 对外接口
282      * @param maxLength 缓存容量
283      * @param dev 是否为开发环境,开发环境会统计命中率,反之不会
284      * @returns {{get, set: Function, hitRatio: Function}}
285      */
286     exports.initTwoQueues = function(maxLength, dev){
287
288         var api = new Agent(maxLength);
289
290         return {
291             get: (function(){
292                 if(dev){
293                     return function(key){
294                         var ret = api.getx(key);
295                         return ret && ret.data;
296                     };
297                 }else{
298                     return function(key){
299                         var ret = api.get(key);
300                         return ret && ret.data;
301                     };
302                 }
303             }()),
304             set: function(){
305                 api.set.apply(api, arguments);
306             },
307             hitRatio: function(){
308                 return api.hitRatio.apply(api, arguments);
309             }
310         };
311
312     };
313
314
315 }(this));

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

JavaScript实现TwoQueues缓存模型相关推荐

  1. 基于JVM原理、JMM模型和CPU缓存模型深入理解Java并发编程

    许多以Java多线程开发为主题的技术书籍,都会把对Java虚拟机和Java内存模型的讲解,作为讲授Java并发编程开发的主要内容,有的还深入到计算机系统的内存.CPU.缓存等予以说明.实际上,在实际的 ...

  2. 用JavaScript实现本地缓存

    用JavaScript实现本地缓存 memory.js function window.onerror(){return false;}function pageCache(prefix){this. ...

  3. 6张图为你分析Kafka Producer 消息缓存模型

    本文分享自华为云社区<图解Kafka Producer 消息缓存模型>,作者:石臻臻的杂货铺. 在阅读本文之前, 希望你可以思考一下下面几个问题, 带着问题去阅读文章会获得更好的效果. 发 ...

  4. 在three.js中如何使用indexedDB本地缓存模型

    近期在做three.js相关优化,想实现本地缓存模型功能,缩短模型加载时间. 在网上搜了相关资料,没有找到我想用的,终于研究明白了,下面讲下实现过程,如果有优化建议可以提出来,共同进步! 1. 创建i ...

  5. html流动模型,javascript的事件流模型都有什么?

    事件流:当你在页面触发一个点击事件后,页面上不仅仅有一个元素响应该事件而是多个元素响应同一个事件,因为元素是在容器中的.事件发生的顺序就是事件流,不同的浏览器对事件流的处理不同. JavaScript ...

  6. JavaScript使用localStorage缓存Js和css文件

    对于WebApp来说,将js css文件缓存到localstorage区可以减少页面在加载时与HTTP请求的交互次数,从而优化页面的加载时间.特别是当移端信号不好高延迟时优化效果还是很显见的 下面的代 ...

  7. JavaScript 复习之 事件模型 和 Event对象

    事件模型 一.监听函数 js 有三种方法,可以为事件绑定监听函数 HTML 的 on- 属性 元素节点的事件属性,也可以指定监听函数 EventTarget.addEventListener() DO ...

  8. 分享一个WebGL开发的网站-用JavaScript + WebGL开发3D模型

    这张图每位程序员应该都深有感触. 人民心目中的程序员是这样的:坐在电脑面前噼里啪啦敲着键盘,运键如飞. 现实中程序员是这样的:编码5分钟,调试两小时. 今天我要给大家分享一个用WebGL开发的网站,感 ...

  9. 【前端学习】前端学习第十五天:JavaScript中的事件模型

    在各种浏览器中存在三种事件模型:原始事件模型.DOM事件模型和IE事件模型: 一.原始事件模型: 原始事件模型被所有浏览器支持: 在原始事件模型中.事件一旦发生就直接调用事件处理函数,事件不会向别的对 ...

最新文章

  1. 2020年行政区划代码_2020年柳州市行政区划,了解柳州市有几个区,详细数据
  2. Swift语言中class、struct、enum的联系与区别
  3. 《树莓派渗透测试实战》——总结
  4. php 同步代码,PHP进程同步代码实例
  5. oracle无法打开日志组,ORA-00313:无法打开日志组1(线程 1)的成员_ORA-00312:
  6. 安师大计算机安全网络,计信学院成功举行网络与信息安全安徽省重点实验室2020年度学术年会...
  7. 编程学习记录1:编程的一些简单概念
  8. 网络通讯协议——TCP/IP协议
  9. ubuntu stardict词典安装
  10. ora-20011 ora-01555
  11. 【入门】萌新IP入门常识(一):什么是IP地址和代理IP
  12. C#开发工控上位机编程 csdn_C#联合WINCC之数据通信
  13. 用思维导图赏析老舍话剧著作《茶馆》
  14. 如何反编译 cocos creator 生成 的jsc文件/反编译jsc文件(一)
  15. Unity2D平台开发
  16. Calcite CBO 分析1
  17. Chrome浏览器设置网站前自动加https
  18. android 调用系统文件管理器
  19. 正则表达式判定电话号码(电信移动联通)
  20. Fibonacci数列的递推公式为:Fn=Fn-1+Fn-2,其中F1=F2=1。当n比较大时,Fn也非常大,现在我们想知道,Fn除以10007的余数是多少。(python)

热门文章

  1. 【题解】Luogu P1011 车站
  2. 阶梯到XML:1级 - XML简介
  3. 九大排序算法Java实现
  4. WebService开发中SoapException的用法
  5. 轻松取得建表和索引的DDL语句
  6. C++类实例以及子类在内存中的分配
  7. C# 中的委托和事件(详解) ....
  8. 2022.4.9 mac os M1 芯片 12.3.1 Monterey 安装cocoapods
  9. vba 将html转换excel,利用VBA将不同格式excel模板之间进行数据转换实例
  10. mixin network_基于Mixin Network的Go语言比特币开发教程 : 用 Mixin Messenger 机器人接受和发送比特币...