当前位置: 首页 > news >正文

轻量级node-cache源码分析一波

最近有接触到缓存这一块,无意间发现了这个清凉级的缓存,而且也不复杂,刚好作为源码练习一下,戳此观赏源码 -> 点我,点我

话不多说,直接来看看...

先从如何使用来入手,毕竟这是最直观的


  • put = function(key, value, time, timeoutCallback)
    • 简洁的存储一个value
    • 如果没有带入time,这个value会永久存储
    • 在给定的time之后会移除value,time是毫秒(via setTimeout)
    • timeoutCallback是可选的参数,它会在传入的kye,value过期之后激活
    • 返回缓存的value
  • get = function(key)
    • 在缓存中查找对应key的value
    • 如果缓存中没有value,返回null
  • del = function(key)
    • 删除一个key,返回boolean来判断key是否被删除
  • clear = function()
    • 删除所有的key
  • size = function()
    • 返回当前缓存中的缓存数量
  • memsize = function()
    • 返回当前缓存所占的空间
    • 通常== size(),除非setTimeout出了问题
  • debug = function(bool)
    • 开启/关闭debugging模式
  • hits = function()
    • 返回缓存命中数(只在debug模式下监听)
  • misses = function()
    • 返回缓存未命中数(只在debug模式下监听)
  • keys = funciton()
    • 返回所有缓存中的keys
  • exportJson = function()
    • 将所有缓存数据转成JSON返回
    • 所有timeoutCallbacks将被忽略
  • importJson = function(json:string, options:{skipDuplicates:boolean})
    • 合并所有在之前的调用中export进入缓存的数据
    • import之前的所有已存在的数据都将保存在缓存中
    • skipDuplicatestrue时所有重复的key都将被重写
    • 所有数据被暴露后,在导入时都将过期(但是他们的回调不会执行)
    • 可用options
      • skipDuplicates:如果为true,所有重复的keys在导入的时候都会被跳过。默认为false
    • 返回新的缓存数量
  • Cache = function()
    • Cache构造器
    • 需要注意require('cache')会返回Cache的默认实例
    • require('cache').Cache才是实际的类。

看了这些api,我们大致知道了用法,而且文档也特别详细。相信对阅读源码很是方便,下面直接上。

作者好久没更新了,不过这并不影响它有900多颗星星:smile:, 文件也很直观,需要源码就在index.js里面。

index.js

'use strict';

function Cache () {
  var _cache = Object.create(null);
  var _hitCount = 0;
  var _missCount = 0;
  var _size = 0;
  var _debug = false;

  this.put = function(key, value, time, timeoutCallback) {
    if (_debug) {
      console.log('caching: %s = %j (@%s)', key, value, time);
    }

    if (typeof time !== 'undefined' && (typeof time !== 'number' || isNaN(time) || time <= 0)) {
      throw new Error('Cache timeout must be a positive number');
    } else if (typeof timeoutCallback !== 'undefined' && typeof timeoutCallback !== 'function') {
      throw new Error('Cache timeout callback must be a function');
    }

    var oldRecord = _cache[key];
    if (oldRecord) {
      clearTimeout(oldRecord.timeout);
    } else {
      _size++;
    }

    var record = {
      value: value,
      expire: time + Date.now()
    };

    if (!isNaN(record.expire)) {
      record.timeout = setTimeout(function() {
        _del(key);
        if (timeoutCallback) {
          timeoutCallback(key, value);
        }
      }.bind(this), time);
    }

    _cache[key] = record;

    return value;
  };

  this.del = function(key) {
    var canDelete = true;

    var oldRecord = _cache[key];
    if (oldRecord) {
      clearTimeout(oldRecord.timeout);
      if (!isNaN(oldRecord.expire) && oldRecord.expire < Date.now()) {
        canDelete = false;
      }
    } else {
      canDelete = false;
    }

    if (canDelete) {
      _del(key);
    }

    return canDelete;
  };

  function _del(key){
    _size--;
    delete _cache[key];
  }

  this.clear = function() {
    for (var key in _cache) {
      clearTimeout(_cache[key].timeout);
    }
    _size = 0;
    _cache = Object.create(null);
    if (_debug) {
      _hitCount = 0;
      _missCount = 0;
    }
  };

  this.get = function(key) {
    var data = _cache[key];
    if (typeof data != "undefined") {
      if (isNaN(data.expire) || data.expire >= Date.now()) {
        if (_debug) _hitCount++;
        return data.value;
      } else {
        // free some space
        if (_debug) _missCount++;
        _size--;
        delete _cache[key];
      }
    } else if (_debug) {
      _missCount++;
    }
    return null;
  };

  this.size = function() {
    return _size;
  };

  this.memsize = function() {
    var size = 0,
      key;
    for (key in _cache) {
      size++;
    }
    return size;
  };

  this.debug = function(bool) {
    _debug = bool;
  };

  this.hits = function() {
    return _hitCount;
  };

  this.misses = function() {
    return _missCount;
  };

  this.keys = function() {
    return Object.keys(_cache);
  };

  this.exportJson = function() {
    var plainJsCache = {};

    // Discard the `timeout` property.
    // Note: JSON doesnt support NaN, so convert it to NaN.
    for (var key in _cache) {
      var record = _cache[key];
      plainJsCache[key] = {
        value: record.value,
        expire: record.expire || 'NaN',
      };
    }

    return JSON.stringify(plainJsCache);
  };

  this.importJson = function(jsonToImport, options) {
    var cacheToImport = JSON.parse(jsonToImport);
    var currTime = Date.now();

    var skipDuplicates = options && options.skipDuplicates;

    for (var key in cacheToImport) {
      if (cacheToImport.hasOwnProperty(key)) {
        if (skipDuplicates) {
          var existingRecord = _cache[key];
          if (existingRecord) {
            if (_debug) {
              console.log('Skipping duplicate imported key \'%s\'', key);
            }
            continue;
          }
        }

        var record = cacheToImport[key];

        // record.expire could be `'NaN'` if no expiry was set.
        // Try to subtract from it; a string minus a number is `NaN`, which is perfectly fine here.
        var remainingTime = record.expire - currTime;

        if (remainingTime <= 0) {
          // Delete any record that might exist with the same key, since this key is expired.
          this.del(key);
          continue;
        }

        // Remaining time must now be either positive or `NaN`,
        // but `put` will throw an error if we try to give it `NaN`.
        remainingTime = remainingTime > 0 ? remainingTime : undefined;

        this.put(key, record.value, remainingTime);
      }
    }

    return this.size();
  };
}

module.exports = new Cache();
module.exports.Cache = Cache;

复制代码

这样一看,当然没有头绪呀,这么长一段代码。。。 下面拆分一下:

Part 1:


我们弱化了具体实现,总览一下大概。发现其实Cache只是一个函数而已,通过看最后两行

module.exports = new Cache();
module.exports.Cache = Cache;
复制代码

发现这正好是api:Cache = function()的解释。我们还知道啥?其实这里就可以看出很多东西,Cache里面有_cache, _hitCount, _missCount, _size, _debug这几个变量,不难猜出一些东西:

变量说明
_cache是初始化的一个对象,下面可以使用到
_hitCount用于缓存命中的计数
_missCount用户缓存未命中的计数
_size缓存中的数量
_debug开启debug模式

嗯,除此之外,就是一些属性方法,具体看看实现吧!

Part 2:


下面就看看第一个put方法是做了啥?

  • put = function(key, value, time, timeoutCallback)
    • 简洁的存储一个value
    • 如果没有带入time,这个value会永久存储
    • 在给定的time之后会移除value,time是毫秒(via setTimeout)
    • timeoutCallback是可选的参数,它会在传入的kye,value过期之后激活
    • 返回缓存的value

Part 3:


在看看get方法

  • get = function(key)
    • 在缓存中查找对应key的value
    • 如果缓存中没有value,返回null

Part 4:


剩下的差不多也是和上面类似,就不一一分析啦,直接看:

  • exportJson = function()
    • 将所有缓存数据转成JSON返回
    • 所有timeoutCallbacks将被忽略

  • importJson = function(json:string, options:{skipDuplicates:boolean})
    • 合并所有在之前的调用中export进入缓存的数据
    • import之前的所有已存在的数据都将保存在缓存中
    • skipDuplicatestrue时所有重复的key都将被重写
    • 所有数据被暴露后,在导入时都将过期(但是他们的回调不会执行)
    • 可用options
      • skipDuplicates:如果为true,所有重复的keys在导入的时候都会被跳过。默认为false
    • 返回新的缓存数量

Part 5:


虽然这个node-cache没那么难,写的简洁易懂,也是一种能力呀。就当学习源码的一道开胃菜。

相关文章:

  • 迭代器失效
  • OSChina 周六乱弹 —— 假如你被熊困到树上
  • 改变像素
  • Unix目录结构的来历
  • Localizing WPF with .resx files
  • 转载:进程上下文、中断上下文及原子上下文
  • fstream, operator, operator
  • 图像检索(2):均值聚类-构建BoF
  • 【Under-the-hood-ReactJS-Part0】React源码解读
  • c# is和as的区别
  • 各种面试题 挺好 挺重要 项目中 有用 的地方
  • Nginx 1.正向代理与反向代理
  • C++ 运行单个实例,防止程序多次启动
  • systemC的安装
  • 利用hadoop来解决“共同好友”的问题
  • 【附node操作实例】redis简明入门系列—字符串类型
  • 【译】理解JavaScript:new 关键字
  • Angular数据绑定机制
  • CSS魔法堂:Absolute Positioning就这个样
  • django开发-定时任务的使用
  • ES学习笔记(12)--Symbol
  • Flex布局到底解决了什么问题
  • Netty 4.1 源代码学习:线程模型
  • Python打包系统简单入门
  • Python语法速览与机器学习开发环境搭建
  • Shell编程
  • spring boot 整合mybatis 无法输出sql的问题
  • 创建一个Struts2项目maven 方式
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 订阅Forge Viewer所有的事件
  • 基于 Babel 的 npm 包最小化设置
  • 经典排序算法及其 Java 实现
  • 码农张的Bug人生 - 见面之礼
  • 前端路由实现-history
  • 数据可视化之 Sankey 桑基图的实现
  • 一、python与pycharm的安装
  • 一道面试题引发的“血案”
  • 在weex里面使用chart图表
  • 正则表达式小结
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • #gStore-weekly | gStore最新版本1.0之三角形计数函数的使用
  • #绘制圆心_R语言——绘制一个诚意满满的圆 祝你2021圆圆满满
  • (1)bark-ml
  • (4.10~4.16)
  • (附源码)spring boot车辆管理系统 毕业设计 031034
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (十二)python网络爬虫(理论+实战)——实战:使用BeautfulSoup解析baidu热搜新闻数据
  • (最全解法)输入一个整数,输出该数二进制表示中1的个数。
  • . ./ bash dash source 这五种执行shell脚本方式 区别
  • .cfg\.dat\.mak(持续补充)
  • .jks文件(JAVA KeyStore)