quick-lru
quick-lru LRU(最近最少算法) 缓存的简易实现.
Usage
js
import QuickLRU from 'quick-lru';
const lru = new QuickLRU({maxSize: 1000});
lru.set('🦄', '🌈');
lru.has('🦄');
//=> true
lru.get('🦄');
//=> '🌈'
源码解析
js
export default class QuickLRU extends Map {
constructor(options = {}) {
super();
if (!(options.maxSize && options.maxSize > 0)) {
throw new TypeError('`maxSize` must be a number greater than 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; // 最大项目数
this.maxAge = options.maxAge || Number.POSITIVE_INFINITY; // 项目最大的缓存时间,会在下一次读写的时候判断
this.onEviction = options.onEviction; // 从缓存中移除项之前调用,用于清理副作用
this.cache = new Map(); // 缓存 Map
this.oldCache = new Map(); // 旧的缓存 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;
}
// 过期删除返回 undefined,否则返回缓存项的值
_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) {
if (this.cache.has(key)) {
const item = this.cache.get(key);
return this._getItemValue(key, item);
}
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(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;
}
// 获取缓存项,但是不标记成最近使用
peek(key) {
if (this.cache.has(key)) {
return this._peek(key, this.cache);
}
if (this.oldCache.has(key)) {
return this._peek(key, this.oldCache);
}
}
// 从新旧缓存中删除项
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;
}
// 更新最大缓存数量
resize(newSize) {
if (!(newSize && newSize > 0)) {
throw new TypeError('`maxSize` must be a number greater than 0');
}
const items = [...this._entriesAscending()];
const removeCount = items.length - newSize;
if (removeCount < 0) {
this.cache = new Map(items);
this.oldCache = new Map();
this._size = items.length;
} else {
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;
}
// 获取所有的缓存 key
* 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];
}
}
// 返回新旧缓存的大小
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);
}
// 从最近的缓存项开始返回值和key一组的数组
entries() {
return this.entriesAscending();
}
// 重写 forEach
forEach(callbackFunction, thisArgument = this) {
for (const [key, value] of this.entriesAscending()) {
callbackFunction.call(thisArgument, value, key, this);
}
}
// 将数据转换为 JSON 字符串
get [Symbol.toStringTag]() {
return JSON.stringify([...this.entriesAscending()]);
}
}