Java有LinkedHashMap gets you 99% there to an LRU cache。
是否存在LRU缓存的Javascript实现,最好是来自信誉良好的源,即:
?我一直在网上搜索但找不到一个;我以为我在Ajax Design Patterns上找到了一个,但它掩盖了sendToTail()
方法并具有O(n)性能(可能是因为队列和关联数组被拆分)。
我想我可以写自己的,但我已经知道重新发明核心算法的轮子会对一个人的健康造成危害:/
答案 0 :(得分:8)
此:
https://github.com/monsur/jscache
似乎适合你的情况虽然setItem
(即put)在最坏的情况下是O(N),如果在插入时填充了缓存,则会发生这种情况。在这种情况下,搜索缓存以清除过期的项目或最近最少使用的项目。 getItem
为O(1),并且在getItem
操作上处理到期(即,如果正在提取的项目已过期,则将其删除并返回null)。
代码非常紧凑,易于理解。
P.S。向构造函数添加指定fillFactor
的选项可能很有用,该选项固定为0.75(意味着当清除缓存时,它的大小至少减少到最大大小的3/4)
答案 1 :(得分:6)
这不如您想要的那么高效,但它在简单性和可读性方面弥补了它。在许多情况下,它会很快。
我需要一个简单的LRU缓存来进行少量昂贵的操作(1秒)。我觉得复制粘贴一些小代码而不是介绍一些外部代码感觉更好,但是因为我没有找到它,所以我写了它:
更新:由于我删除了数组,因此现在提高了效率(空间和时间),因为Map保持了插入顺序。
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) {
if (this.cache.has(key)) // refresh key
this.cache.delete(key);
else if (this.cache.size == this.max) // evict oldest
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"
答案 2 :(得分:5)
值得一提的是: https://github.com/rsms/js-lru
核心功能集是O(1),代码评论很多(也有ASCII艺术!)
答案 3 :(得分:3)
monsur.com实现仅在插入时为O(n),因为它具有实际在实际时间到期的项目。它不是一个简单的LRU。如果您只关心维护最近使用的项目而不考虑现实世界时间,可以在O(1)中完成。实现为双向链表的队列是O(1),用于从末尾插入或删除,这是缓存所需的全部内容。至于查找,一个哈希映射,javascript变得非常简单,应该适合于近O(1)查找(假设javascript引擎使用了一个好的hashmap,当然这依赖于实现)。因此,您有一个包含指向项目的哈希映射的项目的链接列表。根据需要操作链表的末尾,将新项目和请求的项目放在一端,并从另一端删除旧项目。
答案 4 :(得分:1)
此库runtime-memcache在javascript中实现了lru和其他一些缓存方案。
它使用修改后的双链表为get
,set
和remove
实现O(1)。您可以签出非常简单的实现。
答案 5 :(得分:0)
它不是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();
}
答案 6 :(得分:0)
我知道这是一个老问题,但为未来的参考添加了一个链接。 查看https://github.com/monmohan/dsjslib。除了一些其他数据结构之外,它还具有LRU Cache实现。这样的高速缓存(以及这个高速缓存)也以LRU顺序维护高速缓存条目的双向链接列表,即当条目被访问时条目移动到头部,并且当它们被回收时(例如通过到期或因为达到大小限制)从尾部移除。它的O(1)因为它只涉及恒定数量的指针操作。
答案 7 :(得分:0)
由于我们需要在O(1)中进行读取,写入,更新和删除操作,因此我们使用两个数据结构。
下面给出了双向链接列表和最近最少使用缓存的自定义实现,
https://medium.com/dsinjs/implementing-lru-cache-in-javascript-94ba6755cda9
答案 8 :(得分:0)
不需要外部程序包/库,我们可以编写自己的代码以用javascript实现LRU,有关详细信息,请访问https://dev.to/udayvunnam/implementing-lru-cache-in-javascript-3c8g网站。
答案 9 :(得分:0)
(这只是一个长评论)
这是一个时钟(它是 LRU 近似)缓存,它只使用字符串/整数作为键,并且只有一个 get 方法:
O(1) 缓存命中,无节点移动(垃圾收集友好)
O(1) 缓存未命中,异步
异步访问器(或异步缓存未命中)的数量需要等于或小于缓存大小,否则会发生临时死锁
lrucache.js:
'use strict';
/*
cacheSize: number of elements in cache, constant, must be greater than or equal to number of asynchronous accessors / cache misses
callbackBackingStoreLoad: user-given cache-miss function to load data from datastore
elementLifeTimeMs: maximum miliseconds before an element is invalidated, only invalidated at next get() call with its key
*/
let Lru = function(cacheSize,callbackBackingStoreLoad,elementLifeTimeMs=1000){
const me = this;
const maxWait = elementLifeTimeMs;
const size = parseInt(cacheSize,10);
const mapping = {};
const mappingInFlightMiss = {};
const bufData = new Array(size);
const bufVisited = new Uint8Array(size);
const bufKey = new Array(size);
const bufTime = new Float64Array(size);
const bufLocked = new Uint8Array(size);
for(let i=0;i<size;i++)
{
let rnd = Math.random();
mapping[rnd] = i;
bufData[i]="";
bufVisited[i]=0;
bufKey[i]=rnd;
bufTime[i]=0;
bufLocked[i]=0;
}
let ctr = 0;
let ctrEvict = parseInt(cacheSize/2,10);
const loadData = callbackBackingStoreLoad;
let inFlightMissCtr = 0;
this.get = function(keyPrm,callbackPrm){
const key = keyPrm;
const callback = callbackPrm;
// stop dead-lock when many async get calls are made
if(inFlightMissCtr>=size)
{
setTimeout(function(){
me.get(key,function(newData){
callback(newData);
});
},0);
return;
}
// delay the request towards end of the cache-miss completion
if(key in mappingInFlightMiss)
{
setTimeout(function(){
me.get(key,function(newData){
callback(newData);
});
},0);
return;
}
if(key in mapping)
{
let slot = mapping[key];
// RAM speed data
if((Date.now() - bufTime[slot]) > maxWait)
{
if(bufLocked[slot])
{
setTimeout(function(){
me.get(key,function(newData){
callback(newData);
});
},0);
}
else
{
delete mapping[key];
me.get(key,function(newData){
callback(newData);
});
}
}
else
{
bufVisited[slot]=1;
bufTime[slot] = Date.now();
callback(bufData[slot]);
}
}
else
{
// datastore loading + cache eviction
let ctrFound = -1;
while(ctrFound===-1)
{
// give slot a second chance before eviction
if(!bufLocked[ctr] && bufVisited[ctr])
{
bufVisited[ctr]=0;
}
ctr++;
if(ctr >= size)
{
ctr=0;
}
// eviction conditions
if(!bufLocked[ctrEvict] && !bufVisited[ctrEvict])
{
// evict
bufLocked[ctrEvict] = 1;
inFlightMissCtr++;
ctrFound = ctrEvict;
}
ctrEvict++;
if(ctrEvict >= size)
{
ctrEvict=0;
}
}
mappingInFlightMiss[key]=1;
let f = function(res){
delete mapping[bufKey[ctrFound]];
bufData[ctrFound]=res;
bufVisited[ctrFound]=0;
bufKey[ctrFound]=key;
bufTime[ctrFound]=Date.now();
bufLocked[ctrFound]=0;
mapping[key] = ctrFound;
callback(bufData[ctrFound]);
inFlightMissCtr--;
delete mappingInFlightMiss[key];
};
loadData(key,f);
}
};
};
exports.Lru = Lru;
基于时间的失效仅在 cache.get() 操作期间发生,因此它不会对任何元素使用事件引擎。
示例用法:
let Lru = require("./lrucache.js").Lru;
let num_cache_elements = 1000;
let element_life_time_miliseconds = 1000;
let cache = new Lru(num_cache_elements, async function(key,callback){
// datastore access for filling the missing cache element when user access key
callback(some_time_taking_io_work_or_heavy_computation(key));
}, element_life_time_miliseconds);
cache.get("some_key_string",function(data){
// data comes from datastore or RAM depending on its lifetime left or the key acceess pattern
// do_something_with(data);
});
文件缓存示例:
let Lru = require("./lrucache.js").Lru;
let fs = require("fs");
let path = require("path");
let fileCache = new Lru(500, async function(key,callback){
// cache-miss data-load algorithm
fs.readFile(path.join(__dirname,key),function(err,data){
if(err) {
callback({stat:404, data:JSON.stringify(err)});
}
else
{
callback({stat:200, data:data});
}
});
},1000 /* cache element lifetime */);
// test with a file
// cache-miss
let t1 = Date.now();
fileCache.get("./test.js",function(dat){
console.log("Cache-miss time:");
console.log(Date.now()-t1);
console.log("file data:");
console.log(dat.data.length+" bytes");
// cache-hit
t1 = Date.now();
fileCache.get("./test.js",function(dat){
console.log("Cache-hit time:");
console.log(Date.now()-t1);
console.log("file data:");
console.log(dat.data.length+" bytes");
});
});
异步缓存未命中基准:
let Lru = require("./lrucache.js").Lru;
let benchSize = 500;
let cacheMiss= 0;
let cacheAccess= 0;
let cache = new Lru(900, async function(key,callback){
// cache-miss data-load algorithm
setTimeout(function(){
cacheMiss++
callback(key+" processed");
},1000);
},1000 /* cache element lifetime */);
let ctrBench = 0;
function bench()
{
let ctr = 0;
let t1 = Date.now();
for(let i=0;i<benchSize;i++)
{
let key = parseInt(Math.random()*1000,10);
cache.get(key,function(data){
cacheAccess++;
if(key.toString()+" processed" !== data)
{
console.log("error: wrong key-data mapping.");
}
if(++ctr === benchSize)
{
console.log("benchmark: "+(Date.now()-t1)+" miliseconds");
console.log("cache hit: "+(cacheAccess-cacheMiss));
console.log("cache miss: "+(cacheMiss));
if(ctrBench<20)
{
ctrBench++
bench();
}
}
});
}
}
bench();
输出:
benchmark: 1034 miliseconds
cache hit: 125
cache miss: 375
benchmark: 1015 miliseconds
cache hit: 374
cache miss: 626
benchmark: 1017 miliseconds
cache hit: 571
cache miss: 929
benchmark: 1013 miliseconds
cache hit: 750
cache miss: 1250
benchmark: 1016 miliseconds
cache hit: 950
cache miss: 1550
benchmark: 1017 miliseconds
cache hit: 1132
cache miss: 1868
benchmark: 1013 miliseconds
cache hit: 1324
cache miss: 2176
benchmark: 1014 miliseconds
cache hit: 1485
cache miss: 2515
benchmark: 1016 miliseconds
cache hit: 1675
cache miss: 2825
benchmark: 1016 miliseconds
cache hit: 1872
cache miss: 3128
benchmark: 1012 miliseconds
cache hit: 2053
cache miss: 3447
benchmark: 1015 miliseconds
cache hit: 2224
cache miss: 3776
benchmark: 1015 miliseconds
cache hit: 2405
cache miss: 4095
benchmark: 1016 miliseconds
cache hit: 2580
cache miss: 4420
benchmark: 1015 miliseconds
cache hit: 2760
cache miss: 4740
benchmark: 1013 miliseconds
cache hit: 2930
cache miss: 5070
benchmark: 1047 miliseconds
cache hit: 3105
cache miss: 5395
benchmark: 1015 miliseconds
cache hit: 3280
cache miss: 5720
benchmark: 1012 miliseconds
cache hit: 3449
cache miss: 6051
benchmark: 1015 miliseconds
cache hit: 3636
cache miss: 6364
benchmark: 1016 miliseconds
cache hit: 3818
cache miss: 6682
1000 毫秒的寿命在这里降低了缓存命中率。否则预计命中率为 90%。