Javascript 中的 LRU 缓存实现
Posted
技术标签:
【中文标题】Javascript 中的 LRU 缓存实现【英文标题】:LRU cache implementation in Javascript 【发布时间】:2010-11-03 01:09:43 【问题描述】:Java 有 gets you 99% there to an LRU cache 的 LinkedHashMap。
是否有 LRU 缓存的 javascript 实现,最好来自信誉良好的来源,即:
-
可以理解
高效(摊销 O(1) 获取/放置/删除)
?我一直在网上搜索,但找不到;我以为我在 Ajax Design Patterns 上找到了一个,但它掩盖了 sendToTail()
方法并且具有 O(n) 性能(大概是因为队列和关联数组是分开的)。
我想我可以自己写,但我已经明白,为核心算法重新发明***可能会危害一个人的健康:/
【问题讨论】:
使用循环缓冲区和 Map 对象,这是垃圾收集友好和异步缓存未命中:github.com/tugrul512bit/LruJS/blob/main/lrucache.js(它是 LRU 的 CLOCK-2-hand 版本)。只有 1 颗星,所以它比零星声誉更好:) 【参考方案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) 中的读、写、更新和删除操作,我们使用两种数据结构。
-
JavaScript 中的对象(或映射)在 O(1) 中提供检索。
双向链表(我们创建的自定义数据结构)在 O(1) 中实现以下功能
将最常用元素的位置更改为顶部
在达到缓存限制时从缓存中删除最少使用的元素。
Doubly LinkedList和最近最少使用缓存的自定义实现,下面给出了清晰的解释。
https://medium.com/dsinjs/implementing-lru-cache-in-javascript-94ba6755cda9
【讨论】:
不要链接到外部资源,在答案中内嵌你的实现。 如果我没记错的话,这个实现将会失败,多次对同一个键进行 put/write —— 缓存中的每个元素都将用于同一个键,这不是 LRU 应该如何工作的 【参考方案8】:这个库runtime-memcache 在 javascript 中实现了 lru 和其他一些缓存方案。
它使用修改后的双向链表来实现 get
、set
和 remove
的 O(1)。您可以查看非常简单的实现。
【讨论】:
有趣...您能否评论任何有助于某人证明使用该库的合理性的测试/同行评审? (我的问题是从 2009 年开始的,我不知道我当时在做什么,但也许你的回答可以帮助其他人)【参考方案9】:不需要外部包/库,我们可以自己编写代码用javascript实现LRU,详情请参考https://dev.to/udayvunnam/implementing-lru-cache-in-javascript-3c8g站点。
【讨论】:
请用一些额外的信息解释你的答案,而不仅仅是外部来源的链接。 这与this other answer 的答案相同。另一个答案是原作者在一年前发布的以上是关于Javascript 中的 LRU 缓存实现的主要内容,如果未能解决你的问题,请参考以下文章