最简单的LRU算法实现(TJ出品) Nodejs版
最简单的LRU算法实现(TJ出品) Nodejs版
在TJ的Connect模块中看到Cache中一个LRU算法,太简单了,一起品一下:
Cache.prototype.add = function(key){
// initialize store
var len = this.keys.push(key);//在数组最后添加一个元素
// limit reached, invalidate LRU
if (len > this.limit) this.remove(this.keys.shift());//删除数组的第一个元素
var arr = this.store[key] = [];//这里为什么赋值为空数组???
arr.createdAt = new Date;
return arr;
};
好的,让我们来详细分析并实现一个简单的LRU(Least Recently Used)缓存算法。这个算法将根据最近使用的频率来管理缓存中的条目,并且会移除最不常用的条目。
简介
LRU缓存是一种常见的缓存替换策略,它通过移除最久未被访问的条目来保持缓存的大小在一定的限制范围内。这种策略非常适合那些经常访问但数据量较大的场景。
实现代码
我们来实现一个简单的LRU缓存类。这个类包含一个缓存存储(store
),一个键列表(keys
),以及缓存的最大容量(limit
)。以下是具体的实现:
class LRUCache {
constructor(limit) {
this.limit = limit;
this.store = {};
this.keys = [];
}
add(key, value) {
// 如果key已经存在,则将其移动到keys的末尾
if (this.keys.includes(key)) {
const index = this.keys.indexOf(key);
this.keys.splice(index, 1);
this.keys.push(key);
} else {
// 否则,如果缓存已满,则移除最不常用的条目
if (this.keys.length >= this.limit) {
const lruKey = this.keys.shift();
delete this.store[lruKey];
}
this.keys.push(key);
}
// 添加或更新缓存条目
this.store[key] = { value, createdAt: new Date() };
}
get(key) {
if (!this.store[key]) {
return undefined;
}
// 将访问过的key移到keys的末尾
const index = this.keys.indexOf(key);
this.keys.splice(index, 1);
this.keys.push(key);
return this.store[key].value;
}
}
// 使用示例
const cache = new LRUCache(3);
cache.add('a', 'valueA');
cache.add('b', 'valueB');
cache.add('c', 'valueC');
console.log(cache.get('a')); // 输出 valueA
cache.add('d', 'valueD'); // 这将导致 'b' 被移除,因为它是最近最少使用的
console.log(cache.get('b')); // 输出 undefined
解释
add
方法用于向缓存中添加新的键值对。如果键已经存在于缓存中,则将其从keys
列表中移除并添加到末尾,表示该键最近被使用。- 如果缓存已满,则移除
keys
列表中的第一个元素(即最近最少使用的键),然后将新键添加到末尾。 get
方法用于获取缓存中的值。如果键存在,则将其从keys
列表中移除并添加到末尾,表示该键最近被访问过。
通过这种方式,我们可以有效地管理和维护一个固定大小的LRU缓存。
这个模块他都没怎么用吧。
staticCache即采用cache模块LRU算法,给出的性能测试数据如下:
- static(): 2700 rps
- node-static: 5300 rps
- static() + staticCache(): 7500 rps staticCache比node-static快 29%,并且支持LRU算法。
因此组合使用static()+staticCache()应该能够获得更好的性能。【以上数据作者TJ的说明,我并没有验证】
生产环境上不行的
抛弃if (len > this.limit) this.remove(this.keys.shift());//删除数组的第一个元素 这段来看 后面的就是一个栈的数据存取的方式 可能是始终这个对象的keys的length固定在this.limit中 而var arr = this.store[key] = [];//这里为什么赋值为空数组??? this.keys.push(key) 添加对象的key var arr = this.store[key] = [] 根据当前的key定义一个数据结构
var arr = this.store[key] = [];//这里为什么赋值为空数组???
个人理解:将this.store[key]作为一个数组返回出去,外部可以做更多事情(相比字符串),灵活性好一点。
在TJ大神的Connect模块中,有一个非常简洁的LRU(Least Recently Used)缓存实现。这种实现方式非常高效且易于理解。下面是对这段代码的详细解析以及如何使用该LRU算法。
代码解析
Cache.prototype.add = function(key) {
// 将键添加到keys数组中
var len = this.keys.push(key);
// 如果缓存大小超过限制,则移除最不常用的项
if (len > this.limit) {
this.remove(this.keys.shift());
}
// 创建存储对象,并记录创建时间
var arr = this.store[key] = [];
arr.createdAt = new Date();
return arr;
};
代码说明
-
this.keys.push(key)
:keys
是一个数组,用于存储所有的键。当一个新的键被添加时,它会被添加到数组的末尾。
-
if (len > this.limit) { ... }
:- 如果
keys
数组的长度超过了设定的最大限制 (this.limit
),则需要移除最不常用的一个键。这里通过移除数组的第一个元素来实现,因为这个元素是最先进入数组的,也就是最不常用的。
- 如果
-
var arr = this.store[key] = [];
:store
是一个对象,用于存储实际的数据。对于每个键,我们创建一个空数组并将其赋值给对应的键。这里的空数组可以用来存放与键相关的数据或状态信息。
-
arr.createdAt = new Date();
:- 记录当前项的创建时间,方便后续根据时间进行排序或淘汰策略。
示例用法
const cache = {
keys: [],
store: {},
limit: 5 // 缓存大小限制为5
};
cache.add('key1');
cache.add('key2');
console.log(cache.keys); // ['key1', 'key2']
console.log(cache.store); // {'key1': [], 'key2': []}
这样,每次调用 add
方法时,都会将新的键添加到 keys
数组中,并更新 store
对象。如果缓存大小超过限制,就会移除最不常用的键。
总结
这个简单的LRU实现方法利用了一个数组和一个对象,通过维护一个键列表和一个存储对象,实现了缓存的管理和淘汰策略。这种方式简洁高效,非常适合用于小型项目或需要简单缓存功能的场景。