JavaScript实现TwoQueues缓存模型
本文所指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缓存模型相关推荐
- 基于JVM原理、JMM模型和CPU缓存模型深入理解Java并发编程
许多以Java多线程开发为主题的技术书籍,都会把对Java虚拟机和Java内存模型的讲解,作为讲授Java并发编程开发的主要内容,有的还深入到计算机系统的内存.CPU.缓存等予以说明.实际上,在实际的 ...
- 用JavaScript实现本地缓存
用JavaScript实现本地缓存 memory.js function window.onerror(){return false;}function pageCache(prefix){this. ...
- 6张图为你分析Kafka Producer 消息缓存模型
本文分享自华为云社区<图解Kafka Producer 消息缓存模型>,作者:石臻臻的杂货铺. 在阅读本文之前, 希望你可以思考一下下面几个问题, 带着问题去阅读文章会获得更好的效果. 发 ...
- 在three.js中如何使用indexedDB本地缓存模型
近期在做three.js相关优化,想实现本地缓存模型功能,缩短模型加载时间. 在网上搜了相关资料,没有找到我想用的,终于研究明白了,下面讲下实现过程,如果有优化建议可以提出来,共同进步! 1. 创建i ...
- html流动模型,javascript的事件流模型都有什么?
事件流:当你在页面触发一个点击事件后,页面上不仅仅有一个元素响应该事件而是多个元素响应同一个事件,因为元素是在容器中的.事件发生的顺序就是事件流,不同的浏览器对事件流的处理不同. JavaScript ...
- JavaScript使用localStorage缓存Js和css文件
对于WebApp来说,将js css文件缓存到localstorage区可以减少页面在加载时与HTTP请求的交互次数,从而优化页面的加载时间.特别是当移端信号不好高延迟时优化效果还是很显见的 下面的代 ...
- JavaScript 复习之 事件模型 和 Event对象
事件模型 一.监听函数 js 有三种方法,可以为事件绑定监听函数 HTML 的 on- 属性 元素节点的事件属性,也可以指定监听函数 EventTarget.addEventListener() DO ...
- 分享一个WebGL开发的网站-用JavaScript + WebGL开发3D模型
这张图每位程序员应该都深有感触. 人民心目中的程序员是这样的:坐在电脑面前噼里啪啦敲着键盘,运键如飞. 现实中程序员是这样的:编码5分钟,调试两小时. 今天我要给大家分享一个用WebGL开发的网站,感 ...
- 【前端学习】前端学习第十五天:JavaScript中的事件模型
在各种浏览器中存在三种事件模型:原始事件模型.DOM事件模型和IE事件模型: 一.原始事件模型: 原始事件模型被所有浏览器支持: 在原始事件模型中.事件一旦发生就直接调用事件处理函数,事件不会向别的对 ...
最新文章
- 2020年行政区划代码_2020年柳州市行政区划,了解柳州市有几个区,详细数据
- Swift语言中class、struct、enum的联系与区别
- 《树莓派渗透测试实战》——总结
- php 同步代码,PHP进程同步代码实例
- oracle无法打开日志组,ORA-00313:无法打开日志组1(线程 1)的成员_ORA-00312:
- 安师大计算机安全网络,计信学院成功举行网络与信息安全安徽省重点实验室2020年度学术年会...
- 编程学习记录1:编程的一些简单概念
- 网络通讯协议——TCP/IP协议
- ubuntu stardict词典安装
- ora-20011 ora-01555
- 【入门】萌新IP入门常识(一):什么是IP地址和代理IP
- C#开发工控上位机编程 csdn_C#联合WINCC之数据通信
- 用思维导图赏析老舍话剧著作《茶馆》
- 如何反编译 cocos creator 生成 的jsc文件/反编译jsc文件(一)
- Unity2D平台开发
- Calcite CBO 分析1
- Chrome浏览器设置网站前自动加https
- android 调用系统文件管理器
- 正则表达式判定电话号码(电信移动联通)
- Fibonacci数列的递推公式为:Fn=Fn-1+Fn-2,其中F1=F2=1。当n比较大时,Fn也非常大,现在我们想知道,Fn除以10007的余数是多少。(python)
热门文章
- 【题解】Luogu P1011 车站
- 阶梯到XML:1级 - XML简介
- 九大排序算法Java实现
- WebService开发中SoapException的用法
- 轻松取得建表和索引的DDL语句
- C++类实例以及子类在内存中的分配
- C# 中的委托和事件(详解) ....
- 2022.4.9 mac os M1 芯片 12.3.1 Monterey 安装cocoapods
- vba 将html转换excel,利用VBA将不同格式excel模板之间进行数据转换实例
- mixin network_基于Mixin Network的Go语言比特币开发教程 : 用 Mixin Messenger 机器人接受和发送比特币...