持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
quick-lru
这是个用map对象来进行lru的模拟,不同于链表,用两个cache进行存储,有点奇异,简单理解下。
链表的参考 lru-cache www.yuque.com/ruochuan12/…
参考
leedcode刷题理解
// 正统应该是链表作法,我这取巧用的map,
// 链表作法: https://leetcode.cn/problems/lru-cache/solution/dai-ma-jian-ji-yi-chong-huan-bu-cuo-de-j-n7rj/
var LRUCache = function(capacity) {
this.capacity = capacity
this.map = new Map()
//map 对象 存键是按顺序存的,如果覆盖的话不会改顺序,需要删掉原键,新增。删掉后map对象大小会自动调整。
//删掉第一个键 可以通过map.keys().next().value得到。
};
LRUCache.prototype.get = function(key) {
let map = this.map
let have = map.has(key)
if(have){
//如果有 需要调整顺序,删掉 重新添加
let value = map.get(key)
map.delete(key)
map.set(key,value)
return value
}
return -1
};
LRUCache.prototype.put = function(key, value) {
// 如果满了 但可能加的是重复的,所以可以先不减,先判断是否重复
let map = this.map
let have = map.has(key)
// if(have){
// map.delete(key)
// }else{
// if(this.capacity == map.size()){
// map.delete(map.keys().next().value)
// }
// }
if(have){
map.delete(key)
}
if(this.capacity == map.size){
map.delete(map.keys().next().value)
}
map.set(key,value)
};
api
api:www.yuque.com/ruochuan12/…
源码
export default class QuickLRU extends Map {
//继承map的方法
constructor(options = {}) {
super();
// 传入的最大空间要大于0且存在
if (!(options.maxSize && options.maxSize > 0)) {
throw new TypeError('`maxSize` must be a number greater than 0');
}
// 最长存储时间要是数字类型且非0,额,不是应该大于0吗
if (typeof options.maxAge === 'number' && options.maxAge === 0) {
throw new TypeError('`maxAge` must be a number greater than 0');
}
// TODO: Use private class fields when ESLint supports them.
this.maxSize = options.maxSize;
// Number.POSITIVE_INFINITY 无穷大
this.maxAge = options.maxAge || Number.POSITIVE_INFINITY;
// 数据清除回调
this.onEviction = options.onEviction;
// 两个cache 用来存数据
this.cache = new Map();
this.oldCache = new Map();
// 当前容量
this._size = 0;
}
// TODO: Use private class methods when targeting Node.js 16.
//用来将要清除的数据 进行 数据清除回调
_emitEvictions(cache) {
if (typeof this.onEviction !== 'function') {
return;
}
for (const [key, item] of cache) {
this.onEviction(key, item.value);
}
}
// 校验数据是否过期,过期去执行 数据清除回调 并删掉这个数据
_deleteIfExpired(key, item) {
if (typeof item.expiry === 'number' && item.expiry <= Date.now()) {
if (typeof this.onEviction === 'function') {
this.onEviction(key, item.value);
}
return this.delete(key);
}
return false;
}
// 拿数据,如果过期就删了
_getOrDeleteIfExpired(key, item) {
const deleted = this._deleteIfExpired(key, item);
if (deleted === false) {
return item.value;
}
}
_getItemValue(key, item) {
return item.expiry ? this._getOrDeleteIfExpired(key, item) : item.value;
}
_peek(key, cache) {
const item = cache.get(key);
return this._getItemValue(key, item);
}
_set(key, value) {
this.cache.set(key, value);
this._size++;
if (this._size >= this.maxSize) {
this._size = 0;
this._emitEvictions(this.oldCache);
this.oldCache = this.cache;
this.cache = new Map();
}
}
_moveToRecent(key, item) {
this.oldCache.delete(key);
this._set(key, item);
}
* _entriesAscending() {
for (const item of this.oldCache) {
const [key, value] = item;
if (!this.cache.has(key)) {
const deleted = this._deleteIfExpired(key, value);
if (deleted === false) {
yield item;
}
}
}
for (const item of this.cache) {
const [key, value] = item;
const deleted = this._deleteIfExpired(key, value);
if (deleted === false) {
yield item;
}
}
}
//取值方法
get(key) {
// cache如果有,拿出来,
if (this.cache.has(key)) {
const item = this.cache.get(key);
// 如果过期了,删掉数据,啥也没返回;没过期,给值
return this._getItemValue(key, item);
}
// oldCache如果有,如果过期了,删掉数据,啥也没返回;没过期,给值,还要把值删掉,加到cache里
if (this.oldCache.has(key)) {
const item = this.oldCache.get(key);
if (this._deleteIfExpired(key, item) === false) {
this._moveToRecent(key, item);
return item.value;
}
}
}
// 存值方法,存的时候算出过期时间一起存进去,如果是重复数据,覆盖即可。
// 如果是新数据,走_set,判断加入后size是否满了,满了就把oldCache数据进行数据清除回调,把cache给oldCache,size和cache重置,这作法我感觉怪怪的。
set(key, value, {maxAge = this.maxAge} = {}) {
const expiry =
typeof maxAge === 'number' && maxAge !== Number.POSITIVE_INFINITY ?
Date.now() + maxAge :
undefined;
if (this.cache.has(key)) {
this.cache.set(key, {
value,
expiry
});
} else {
this._set(key, {value, expiry});
}
}
// 判断,过期了就要删掉
has(key) {
if (this.cache.has(key)) {
return !this._deleteIfExpired(key, this.cache.get(key));
}
if (this.oldCache.has(key)) {
return !this._deleteIfExpired(key, this.oldCache.get(key));
}
return false;
}
// 不会影响cache和oldCache数据,取值过期了还是会删掉的。
peek(key) {
if (this.cache.has(key)) {
return this._peek(key, this.cache);
}
if (this.oldCache.has(key)) {
return this._peek(key, this.oldCache);
}
}
// cache删完old去删。
delete(key) {
const deleted = this.cache.delete(key);
if (deleted) {
this._size--;
}
return this.oldCache.delete(key) || deleted;
}
// 重置
clear() {
this.cache.clear();
this.oldCache.clear();
this._size = 0;
}
// 调整cache、oldCache中元素数量
resize(newSize) {
if (!(newSize && newSize > 0)) {
throw new TypeError('`maxSize` must be a number greater than 0');
}
// 集合oldcache和cache,old里面找cache里没有的且没过期的,cache里找没过期的
const items = [...this._entriesAscending()];
// 给的newSize大于数据总量,oldcache重置,数据给cache
const removeCount = items.length - newSize;
if (removeCount < 0) {
this.cache = new Map(items);
this.oldCache = new Map();
this._size = items.length;
} else {
// 容量不够的话,把old里多的进行删除回调(item前几个),剩下的全塞old里,cache重置。
if (removeCount > 0) {
this._emitEvictions(items.slice(0, removeCount));
}
this.oldCache = new Map(items.slice(removeCount));
this.cache = new Map();
this._size = 0;
}
this.maxSize = newSize;
}
// yield 函数,还久没碰到了
* keys() {
for (const [key] of this) {
yield key;
}
}
* values() {
for (const [, value] of this) {
yield value;
}
}
* [Symbol.iterator]() {
for (const item of this.cache) {
const [key, value] = item;
const deleted = this._deleteIfExpired(key, value);
if (deleted === false) {
yield [key, value.value];
}
}
for (const item of this.oldCache) {
const [key, value] = item;
if (!this.cache.has(key)) {
const deleted = this._deleteIfExpired(key, value);
if (deleted === false) {
yield [key, value.value];
}
}
}
}
* entriesDescending() {
let items = [...this.cache];
for (let i = items.length - 1; i >= 0; --i) {
const item = items[i];
const [key, value] = item;
const deleted = this._deleteIfExpired(key, value);
if (deleted === false) {
yield [key, value.value];
}
}
items = [...this.oldCache];
for (let i = items.length - 1; i >= 0; --i) {
const item = items[i];
const [key, value] = item;
if (!this.cache.has(key)) {
const deleted = this._deleteIfExpired(key, value);
if (deleted === false) {
yield [key, value.value];
}
}
}
}
* entriesAscending() {
for (const [key, value] of this._entriesAscending()) {
yield [key, value.value];
}
}
// 搞两个cache自然就会很难算这个size,
// 如果_size空了,返回old的
// 如果_size有,判断old里cache没的有多少个,加上_size返回
get size() {
if (!this._size) {
return this.oldCache.size;
}
let oldCacheSize = 0;
for (const key of this.oldCache.keys()) {
if (!this.cache.has(key)) {
oldCacheSize++;
}
}
return Math.min(this._size + oldCacheSize, this.maxSize);
}
entries() {
return this.entriesAscending();
}
forEach(callbackFunction, thisArgument = this) {
for (const [key, value] of this.entriesAscending()) {
callbackFunction.call(thisArgument, value, key, this);
}
}
get [Symbol.toStringTag]() {
return JSON.stringify([...this.entriesAscending()]);
}
}
总结
我真的觉得这种两个map对象存值的很别扭。
set 时,如果new存满了,把数据全给old,new置空
get 时,需要 new 看一下,old也看一下
delete 同上。
size 更是 需要两边加。
虽然lur-cache
代码可能更复杂点,但更好懂。
挑了不少的代码主题,因为喜欢写注释详解,找了些注释比较清晰的主题
an-old-hope
github-gist
gml
googlecode 代码太浅
hopscotch
lioshi
vs
xcode