Gahing's blog Gahing's blog
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Gahing / francecil

To be best
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 前端基础

  • 应用框架

  • 工程能力

  • 应用基础

  • 专业领域

    • 服务端

      • Deno

      • Node.js

        • Node.js 版本切换
        • Node子进程执行ping操作并获取统计信息
        • Node 中的当前目录路径
        • node小bug记录
        • 事件驱动理解
        • 小技巧:Chrome 在线调试 Node
        • 深入浅出Node.js
        • log4js配置详解
        • node 模块源码解析

          • lru-cache
            • 介绍
            • 使用
            • 源码
          • once
          • yallist
        • nodejs文件下载
      • 服务端框架

    • 跨端技术

    • Web IDE

    • 中后台

    • 动效渲染

    • 可视化

    • 埋点监控

    • 多媒体

    • 桌面技术

    • 游戏互动

    • 编辑器

    • 虚拟化与容器化

    • 设计系统

  • 业务场景

  • 大前端
  • 专业领域
  • 服务端
  • Node.js
  • node 模块源码解析
gahing
2018-02-24
目录

lru-cache专题

# 介绍

least-recently-used items

最近最少使用策略的缓存框架

  1. 新数据插入到链表头部;

  2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;

  3. 当链表满的时候,将链表尾部的数据丢弃。

采用两个数据结构:Map({key,Node(value:Entry{value:value,maxAge:...})}) 和 List (Node->Node->Node)

往cache存入 k,v时,v先包装成Entry,然后再包装成Node存入List,从List头部取到Node和一开始key 一起放入map

# 使用

var LRU = require("lru-cache")
  , options = { max: 500
              , length: function (n, key) { return n * 2 + key.length }
              , dispose: function (key, n) { n.close() }
              , maxAge: 1000 * 60 * 60 }
  , cache = LRU(options)
  , otherCache = LRU(50) // sets just the max size

cache.set("key", "value")
cache.get("key") // "value"

// non-string keys ARE fully supported
var someObject = {}
cache.set(someObject, 'a value')
cache.set('[object Object]', 'a different value')
assert.equal(cache.get(someObject), 'a value')

cache.reset()    // empty the cache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 源码

set 方法

LRUCache.prototype.set = function (key, value, maxAge) {
  maxAge = maxAge || this[MAX_AGE]

  var now = maxAge ? Date.now() : 0
  //计算欲存储元素的长度
  var len = this[LENGTH_CALCULATOR](value, key)
  // map中存在该元素
  if (this[CACHE].has(key)) {
    //本次添加元素大小大于MAX
    if (len > this[MAX]) {
      //删除原来map中k-v对和list中存的value对应的元素, 并返回false表示添加失败
      del(this, this[CACHE].get(key))
      return false
    }

    var node = this[CACHE].get(key)
    //拿到Node节点中的Entry元素
    var item = node.value

    // dispose of the old one before overwriting
    // split out into 2 ifs for better coverage tracking
    if (this[DISPOSE]) {
      //设置NO_DISPOSE_ON_SET选项后,仅在清除元素时调用dispose方法,不会在重写原始时进行。
      if (!this[NO_DISPOSE_ON_SET]) {
        //该方法主要是用于 元素从cache中删除时的前置操作,比如存了一个文件描述符就需要进行关闭。
        //该方法是前置删除操作,所以如果想在删除时将元素重新放回去,需要配合使用`nextTick` or `setTimeout` ,否则使用不当可能会栈溢出
        this[DISPOSE](key, item.value)
      }
    }
    //更新Entry元素
    item.now = now
    item.maxAge = maxAge
    item.value = value
    this[LENGTH] += len - item.length
    item.length = len
    // get操作会将对应的Node元素移到list头
    this.get(key)
    //控制cache大小
    trim(this)
    return true
  }
  //构建Entry实例
  var hit = new Entry(key, value, len, now, maxAge)

  // 本次添加元素大小大于MAX
  // 不添加,并返回false表示添加失败
  if (hit.length > this[MAX]) {
    if (this[DISPOSE]) {
      this[DISPOSE](key, value)
    }
    return false
  }

  this[LENGTH] += hit.length
  //list中存入Entry
  this[LRU_LIST].unshift(hit)
  //设置cache 注意cache中存的Node和list中的Node是同一个对象
  this[CACHE].set(key, this[LRU_LIST].head)
  //先存再删
  trim(this)
  return true
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

将元素控制在MAX大小内

function trim (self) {
  if (self[LENGTH] > self[MAX]) {
    //从尾部不断向前删除
    for (var walker = self[LRU_LIST].tail;
         self[LENGTH] > self[MAX] && walker !== null;) {
      // We know that we're about to delete this one, and also
      // what the next least recently used key will be, so just
      // go ahead and set it now.
      var prev = walker.prev
      del(self, walker)
      walker = prev
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

get 函数

function get (self, key, doUse) {
  var node = self[CACHE].get(key)
  if (node) {
    var hit = node.value
    //判断是否过期
    if (isStale(self, hit)) {
      del(self, node)
      //设置ALLOW_STALE=true参数后 仍会返回元素
      if (!self[ALLOW_STALE]) hit = undefined
    } else {
      //peek函数 doUse为false 不更新node节点位置
      if (doUse) {
        self[LRU_LIST].unshiftNode(node)
      }
    }
    if (hit) hit = hit.value
  }
  return hit
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

判断是否过期

function isStale (self, hit) {
  //Entry元素为空 或 Entry和map都没有设置最大超时
  if (!hit || (!hit.maxAge && !self[MAX_AGE])) {
    return false
  }
  var stale = false
  var diff = Date.now() - hit.now
  if (hit.maxAge) {
    stale = diff > hit.maxAge
  } else {
    //都未进行超时时间设置 目前感觉都是返回false
    stale = self[MAX_AGE] && (diff > self[MAX_AGE])
  }
  return stale
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
编辑 (opens new window)
上次更新: 2024/09/01, 23:56:56
log4js配置详解
once

← log4js配置详解 once→

最近更新
01
浅谈代码质量与量化指标
08-27
02
快速理解 JS 装饰器
08-26
03
Vue 项目中的 data-v-xxx 是怎么生成的
09-19
更多文章>
Theme by Vdoing | Copyright © 2016-2024 Gahing | 闽ICP备19024221号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式