【问题标题】:LRU cache implementation in JavascriptJavascript 中的 LRU 缓存实现
【发布时间】:2010-11-03 01:09:43
【问题描述】:

Java 有 gets you 99% there to an LRU cache 的 LinkedHashMap。

是否有 LRU 缓存的 Javascript 实现,最好来自信誉良好的来源,即:

  1. 可以理解
  2. 高效(摊销 O(1) 获取/放置/删除)

?我一直在网上搜索,但找不到;我以为我在 Ajax Design Patterns 上找到了一个,但它掩盖了 sendToTail() 方法并且具有 O(n) 性能(大概是因为队列和关联数组是分开的)。

我想我可以自己写,但我已经明白,为核心算法重新发明轮子可能会危害一个人的健康:/

【问题讨论】:

标签: javascript algorithm caching lru


【解决方案1】:

这个:

https://github.com/monsur/jscache

似乎适合你的情况,尽管setItem(即put)在最坏的情况下是O(N),如果缓存在插入时被填满,就会发生这种情况。在这种情况下,将搜索缓存以清除过期项目或最近最少使用的项目。 getItem 是 O(1) 并且过期在 getItem 操作上处理(即,如果正在获取的项目已过期,则将其删除并返回 null)。

代码足够紧凑,易于理解。

附:向构造函数添加指定 fillFactor 的选项可能很有用,该选项固定为 0.75(这意味着当缓存被清除时,它的大小至少减少到最大大小的 3/4)

【讨论】:

  • 谢谢,我确实遇到过。它似乎对我的应用程序有太多的花里胡哨(更不用说 ASP.NET 这句话在我心中是一个巨大的危险信号),但也许我应该再看看它。
  • +1 实现与ASP.NET无关我觉得值得一看
【解决方案2】:

这不是 LRU 缓存,但我有 my own linked map implementation。由于它使用 JS 对象作为存储,因此它具有相似的性能特征(包装对象和散列函数会带来性能损失)。

目前,文档是basically non-existant,但有一个related SO answer

each() 方法将传递当前键、当前值和一个布尔值,指示是否有更多元素作为回调函数的参数。

或者,循环可以通过手动完成

for(var i = map.size; i--; map.next()) {
    var currentKey = map.key();
    var currentValue = map.value();
}

【讨论】:

    【解决方案3】:

    monsur.com 实现仅在插入时为 O(n),因为它的项目实际上在现实世界时间到期。它不是一个简单的 LRU。如果您只关心维护最近使用的项目而不考虑实际时间,则可以在 O(1) 中完成。队列,实现为双向链表,从末尾插入或删除的时间为 O(1),这就是缓存所需的全部内容。至于查找,一个 javascript 让哈希表变得非常容易的哈希映射应该适用于几乎 O(1) 的查找(假设 javascript 引擎使用一个好的哈希映射,这当然取决于实现)。所以你有一个项目的链接列表,其中包含一个指向项目的哈希映射。根据需要操作链表的两端,将新项目和请求项目放在一端,并从另一端移除旧项目。

    【讨论】:

    • 如果项目从 LRU 缓存中删除并重新插入,则链表需要能够从中间删除(但不能插入)项目。这是最难的部分,你的回答似乎掩盖了这一点。
    • 冗余地,从双向链表的中间删除是 O(n),您必须这样做才能保持 LRU 在访问时不变。
    • @Eloff,还有一个额外的哈希映射可以用 O(1) 到达列表中任何位置的任何元素。但是你和“Jason S”是对的,仅仅操纵末端是不够的,列表中任何位置的 any 项目都可以是下一个需要回到前面位置的项目,所以当插入在一端 可以从任何位置移除。不过,感谢哈希映射可以独立于列表的长度。
    【解决方案4】:

    我知道这是一个老问题,但添加了一个链接以供将来参考。 查看https://github.com/monmohan/dsjslib。除了一些其他数据结构之外,它还有一个 LRU 缓存实现。这样的缓存(以及这个缓存)以 LRU 顺序维护缓存条目的双向链表,即条目在被访问时移动到头部,并在它们被回收时从尾部移除(比如到期或达到大小限制)。它的 O(1),因为它只涉及恒定数量的指针操作。

    【讨论】:

      【解决方案5】:

      这值得一提: https://github.com/rsms/js-lru

      核心函数集是 O(1) 并且代码被大量注释(还有 ASCII 艺术!)

      【讨论】:

        【解决方案6】:

        Map should be O(1) in most implementations average case。由于 Map 保持插入顺序,因此在其周围添加一些代码将为您提供 LRU,并且对于大多数用途来说,这应该非常快。

        我需要一个简单的 LRU 缓存来执行少量昂贵的操作(1 秒)。我觉得复制粘贴一些小代码而不是引入外部代码感觉更好,但由于我没有找到它,所以我写了它:

        class LRU {
            constructor(max = 10) {
                this.max = max;
                this.cache = new Map();
            }
        
            get(key) {
                let item = this.cache.get(key);
                if (item) {
                    // refresh key
                    this.cache.delete(key);
                    this.cache.set(key, item);
                }
                return item;
            }
        
            set(key, val) {
                // refresh key
                if (this.cache.has(key)) this.cache.delete(key);
                // evict oldest
                else if (this.cache.size == this.max) this.cache.delete(this.first());
                this.cache.set(key, val);
            }
        
            first() {
                return this.cache.keys().next().value;
            }
        }
        

        用法:

        > let cache = new LRU(3)
        > [1, 2, 3, 4, 5].forEach(v => cache.set(v, 'v:'+v))
        > cache.get(2)
        undefined
        > cache.get(3)
        "v:3"
        > cache.set(6, 6)
        > cache.get(4)
        undefined
        > cache.get(3)
        "v:3"
        

        【讨论】:

        • 你的 this.first() 是如何工作的?地图不提供first()
        • @TaranGoel .first() 在代码中实现。请看下面的set()
        • Awesome @odinho-Velmont,看起来 Map 已经拥有许多功能来帮助我们实现 LRU 缓存。我正在尝试使用简单的 JS 对象(作为哈希表)和 DoublyLinkedList,它变得非常麻烦。
        【解决方案7】:

        由于我们需要 O(1) 中的读、写、更新和删除操作,我们使用两种数据结构。

        1. JavaScript 中的对象(或映射)在 O(1) 中提供检索。
        2. 双向链表(我们创建的自定义数据结构)在 O(1) 中实现以下功能
          • 将最常用元素的位置更改为顶部
          • 在达到缓存限制时从缓存中删除最少使用的元素。

        Doubly LinkedList最近最少使用缓存的自定义实现,下面给出了清晰的解释。

        https://medium.com/dsinjs/implementing-lru-cache-in-javascript-94ba6755cda9

        【讨论】:

        • 不要链接到外部资源,在答案中内嵌你的实现。
        • 如果我没记错的话,这个实现将会失败,多次对同一个键进行 put/write —— 缓存中的每个元素都将用于同一个键,这不是 LRU 应该如何工作的
        【解决方案8】:

        这个库runtime-memcache 在 javascript 中实现了 lru 和其他一些缓存方案。

        它使用修改后的双向链表来实现 getsetremove 的 O(1)。您可以查看非常简单的实现。

        【讨论】:

        • 有趣...您能否评论任何有助于某人证明使用该库的合理性的测试/同行评审? (我的问题是从 2009 年开始的,我不知道我当时在做什么,但也许你的回答可以帮助其他人)
        【解决方案9】:

        不需要外部包/库,我们可以自己编写代码用javascript实现LRU,详情请参考https://dev.to/udayvunnam/implementing-lru-cache-in-javascript-3c8g站点。

        【讨论】:

        • 请用一些额外的信息解释你的答案,而不仅仅是外部来源的链接。
        • 这与this other answer 的答案相同。另一个答案是原作者在一年前发布的
        猜你喜欢
        • 2015-07-19
        • 2021-03-05
        • 1970-01-01
        • 1970-01-01
        • 2015-02-13
        • 1970-01-01
        • 1970-01-01
        • 2011-09-17
        相关资源
        最近更新 更多