本文所指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. * LRU缓存淘汰算法实现
  151. * @constructor
  152. */
  153. function LRU(){
  154. Elimination.apply(this, arguments);
  155. }
  156. LRU.prototype = new Fn();
  157.  
  158. LRU.prototype.get = function(key){
  159. var node = undefined;
  160.  
  161. node = this.container[key];
  162.  
  163. if(node){
  164. this.use(node);
  165. }
  166.  
  167. return node;
  168. };
  169.  
  170. LRU.prototype.set = function(key, value){
  171. var node = this.buildNode(value, key);
  172.  
  173. if(this.length === this.maxLength){
  174. this.pop();
  175. }
  176.  
  177. this.unshift(node);
  178. };
  179.  
  180. /**
  181. * FIFO缓存淘汰算法实现
  182. * @constructor
  183. */
  184. function FIFO(){
  185. Elimination.apply(this, arguments);
  186. }
  187. FIFO.prototype = new Fn();
  188.  
  189. FIFO.prototype.get = function(key){
  190. var node = undefined;
  191.  
  192. node = this.container[key];
  193.  
  194. return node;
  195. };
  196.  
  197. FIFO.prototype.set = function(key, value){
  198. var node = this.buildNode(value, key);
  199.  
  200. if(this.length === this.maxLength){
  201. this.shift();
  202. }
  203.  
  204. this.append(node);
  205. };
  206.  
  207. /**
  208. * LRU、FIFO算法封装,成为新的twoqueues缓存淘汰算法
  209. * @param maxLength
  210. * @constructor
  211. */
  212. function Agent(maxLength){
  213. this.getCount = 0;
  214. this.hitCount = 0;
  215. this.lir = new FIFO(maxLength);
  216. this.hir = new LRU(maxLength);
  217. }
  218.  
  219. Agent.prototype.get = function(key){
  220. var node = undefined;
  221.  
  222. node = this.lir.get(key);
  223.  
  224. if(node){
  225. node.use++;
  226. if(node.use >= 2){
  227. this.lir.remove(node);
  228. this.hir.set(node.key, node.data);
  229. }
  230. }else{
  231. node = this.hir.get(key);
  232. }
  233.  
  234. return node;
  235. };
  236.  
  237. Agent.prototype.getx = function(key){
  238. var node = undefined;
  239.  
  240. this.getCount++;
  241.  
  242. node = this.get(key);
  243.  
  244. if(node){
  245. this.hitCount++;
  246. }
  247.  
  248. return node;
  249. };
  250.  
  251. Agent.prototype.set = function(key, value){
  252. var node = null;
  253.  
  254. node = this.lir.container[key] || this.hir.container[key];
  255.  
  256. if(node){
  257. node.data = value;
  258. }else{
  259. this.lir.set(key, value);
  260. }
  261. };
  262.  
  263. /**
  264. * 获取命中率
  265. * @returns {*}
  266. */
  267. Agent.prototype.hitRatio = function(){
  268. var ret = this.getCount;
  269.  
  270. if(ret){
  271. ret = this.hitCount / this.getCount;
  272. }
  273.  
  274. return ret;
  275. };
  276.  
  277. /**
  278. * 对外接口
  279. * @param maxLength 缓存容量
  280. * @param dev 是否为开发环境,开发环境会统计命中率,反之不会
  281. * @returns {{get, set: Function, hitRatio: Function}}
  282. */
  283. exports.initTwoQueues = function(maxLength, dev){
  284.  
  285. var api = new Agent(maxLength);
  286.  
  287. return {
  288. get: (function(){
  289. if(dev){
  290. return function(key){
  291. var ret = api.getx(key);
  292. return ret && ret.data;
  293. };
  294. }else{
  295. return function(key){
  296. var ret = api.get(key);
  297. return ret && ret.data;
  298. };
  299. }
  300. }()),
  301. set: function(){
  302. api.set.apply(api, arguments);
  303. },
  304. hitRatio: function(){
  305. return api.hitRatio.apply(api, arguments);
  306. }
  307. };
  308.  
  309. };
  310.  
  311. }(this));

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

JavaScript实现TwoQueues缓存模型的更多相关文章

  1. cache4j轻量级java内存缓存框架,实现FIFO、LRU、TwoQueues缓存模型

    简介 cache4j是一款轻量级java内存缓存框架,实现FIFO.LRU.TwoQueues缓存模型,使用非常方便. cache4j为java开发者提供一种更加轻便的内存缓存方案,杀鸡焉用EhCac ...

  2. Backbone.js 为复杂Javascript应用程序提供模型(models)、集合(collections)、视图(views)的结构

    Backbone.js 为复杂Javascript应用程序提供模型(models).集合(collections).视图(views)的结构.其中模型用于绑定键值数据和 自定义事件:集合附有可枚举函数 ...

  3. 一个用于每一天JavaScript示例-使用缓存计算(memoization)为了提高应用程序性能

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...

  4. 写了一个Java的简单缓存模型

    缓存操作接口 /** * 缓存操作接口 * * @author xiudong * * @param <T> */ public interface Cache<T> { /* ...

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

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

  6. 基于.net的分布式系统限流组件 C# DataGridView绑定List对象时,利用BindingList来实现增删查改 .net中ThreadPool与Task的认识总结 C# 排序技术研究与对比 基于.net的通用内存缓存模型组件 Scala学习笔记:重要语法特性

    基于.net的分布式系统限流组件   在互联网应用中,流量洪峰是常有的事情.在应对流量洪峰时,通用的处理模式一般有排队.限流,这样可以非常直接有效的保护系统,防止系统被打爆.另外,通过限流技术手段,可 ...

  7. 【Java并发编程】从CPU缓存模型到JMM来理解volatile关键字

    目录 并发编程三大特性 原子性 可见性 有序性 CPU缓存模型是什么 高速缓存为何出现? 缓存一致性问题 如何解决缓存不一致 JMM内存模型是什么 JMM的规定 Java对三大特性的保证 原子性 可见 ...

  8. 【多线程与高并发原理篇:1_cpu多级缓存模型】

    1. 背景 现代计算机技术中,cpu的计算速度远远高于主内存的读写速度.为了解决速度不匹配问题,充分利用cpu的性能,在cpu与主内存之间加入了多级缓存,也叫高速缓存,cpu读取数据直接从高速缓存中读 ...

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

    摘要:发送消息的时候, 当Broker挂掉了,消息体还能写入到消息缓存中吗? 本文分享自华为云社区<图解Kafka Producer 消息缓存模型>,作者:石臻臻的杂货铺. 在阅读本文之前 ...

随机推荐

  1. Kinect开发随笔①——红外扫描仪(Kinect 数据源)

    来源于 MVA 的 快速入门:Kinect for Windows v2 开发 的学习随笔 具体内容为上图所示章节内容 章节内全部代码:GitHub地址点我(链接失效,待补档) <Page &l ...

  2. 我的 vim 基本配置

    " required 使用 vundle 需要先设置这两项 set nocompatible filetype off " 设置 vundle 插件 使用帮助:https://gi ...

  3. 使用dom4j读取xml连接数据库与之单例模式

    使用dom4j读取xml ,加入jar包 dom4j-1.6.1.jar jaxen-1.1-beta-6.jar public class XmlConfigReader { //懒汉式,延迟加载 ...

  4. 【刷题笔记】I'm stuck! (迷宫)-----java方案

    题目描述 : 给定一个R行C列的地图,地图的每一个方格可能是'#', '+', '-', '|', '.', 'S', 'T'七个字符中的一个,分别表示如下意思: '#': 任何时候玩家都不能移动到此 ...

  5. oracle(sql)基础篇系列(二)——多表连接查询、子查询、视图

        多表连接查询 内连接(inner join) 目的:将多张表中能通过链接谓词或者链接运算符连接起来的数据查询出来. 等值连接(join...on(...=...)) --选出雇员的名字和雇员所 ...

  6. su root认证失败的解决方法

    sudo passwd 输入安装密码. 输入新密码. 输入 su 即获得root权限.

  7. python基础03序列

    sequence 序列 sequence序列是一组有顺序的元素的集合 (严格的说,是对象的集合,但鉴于没有引入对象的概念,暂时说元素) 序列可以包含一个或多个元素,也可以没有任何元素 我们之前所说的基 ...

  8. @Autowired

    1. Spring框架中进行注入式,使用@Autowired. @Autowired可以对成员变量.方法和构造函数进行标注,来完成自动装配的工作,这里必须明确:@Autowired是根据类型进行自动装 ...

  9. Mac系统中配置Tomcat环境

    第一步:下载Tomcat 下载地址:http://tomcat.apache.org/download-80.cgi 直接下载如图选中的即可 第二步: 下载完成后,解压,随意放入目录.如我就把它放在/ ...

  10. IE6 的兼容相关问题

    因为在实习公司要求兼容IE6+,所以将IE6相关的样式兼容问题列出,及解决方案. 1.让页面变丑的透明背景图片问题: HTML都为以下代码: <div class="img-png&q ...