阅读quick-lru源码,了解其原理
LRU
LRU全称为Least Recently Used,也就是最近最少使用的意思,是一种缓存算法策略。
原理
quick-lru内部结构是两个cache(Map数据结构)作为存储,元素内容添加时间戳,设置最大时间限制、最大数量;通过以上几个变量之间的交互来实现添加、修改、删除、遍历等操作。
实现
这里我们从index.d.ts切入,通过查看基本API类型信息,来分析其内部实现原理。
construtor
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();
this.oldCache = new Map();
this._size = 0; // 数量大小
}
set
该api主要用于添加元素,内部通过_set来设置cache、oldcache
set(key, value, {maxAge = this.maxAge} = {}) {
const expiry =
typeof maxAge === 'number' && maxAge !== Number.POSITIVE_INFINITY ?
Date.now() + maxAge :
undefined; // 获取日期
if (this.cache.has(key)) {
// 若存在 cache中,则更新设置值
this.cache.set(key, {
value,
expiry
});
} else {
this._set(key, {value, expiry});
}
}
_set(key, value) {
this.cache.set(key, value); // 添加元素
this._size++; // 元素数量加1
if (this._size >= this.maxSize) {
// 超出数量限制,cache元素赋值到oldCache中,cache重新赋值空map
this._size = 0;
// 超出数目,遍历oldCache中的元素并触发onEviction
this._emitEvictions(this.oldCache);
// oldCache赋值为cache
this.oldCache = this.cache;
// cache赋值为空值
this.cache = new Map();
}
}
delete
map.delete:如果 Map 对象中存在该元素,则移除它并返回 true;否则如果该元素不存在则返回 false
delete(key) {
// cache移除元素
const deleted = this.cache.delete(key);
// 若key被移除,当前元素数量减1
if (deleted) {
this._size--;
}
// oldCache中也要移除key对应的元素
return this.oldCache.delete(key) || deleted;
}
has
判断元素是否存在cache、oldCache,是否已经过期,如已过期,通过_deleteIfExpired进行移除操作
has(key) {
if (this.cache.has(key)) {
// 取反的原因:若key因过期被移除,应当视为当前不存在该key。
return !this._deleteIfExpired(key, this.cache.get(key));
}
if (this.oldCache.has(key)) {
// 原因同上
return !this._deleteIfExpired(key, this.oldCache.get(key));
}
return false;
}
_deleteIfExpired(key, item) {
if (typeof item.expiry === 'number' && item.expiry <= Date.now()) {
// 若元素过期,则需从cache中移除该key
if (typeof this.onEviction === 'function') {
// key移除,触发 移除回调
this.onEviction(key, item.value);
}
// 移除元素
return this.delete(key);
}
return false;
}
get
根据key获取值,内部需要先通过has来进行是否过期判断;oldCache中旧值被使用,应当移到cache中
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) {
// 上述cache中没找到,oldCache中找到,需将oldCache的值移到cache中
this._moveToRecent(key, item);
return item.value;
}
}
}
_getItemValue(key, item) {
// 有时间戳,需要判断是否需要移除,没时间戳,直接返回即可
return item.expiry ? this._getOrDeleteIfExpired(key, item) : item.value;
}
_getOrDeleteIfExpired(key, item) {
const deleted = this._deleteIfExpired(key, item);
if (deleted === false) {
// 不需移除,直接返回该值
return item.value;
}
}
_moveToRecent(key, item) {
// 将 key 从oldCache中移除
this.oldCache.delete(key);
// 将key-item设置到cache中
this._set(key, item);
}
peek
根据key获取值,不同于get的点,这里并不会将oldCache中的值移到cache中
peek(key) {
if (this.cache.has(key)) {
return this._peek(key, this.cache);
}
if (this.oldCache.has(key)) {
return this._peek(key, this.oldCache);
}
}
_peek(key, cache) {
const item = cache.get(key);
return this._getItemValue(key, item);
}
clear
将cache、oldCache中的值清空
clear() {
this.cache.clear();
this.oldCache.clear();
// 元素数量置为0
this._size = 0;
}
resize
调整cache、oldCache中元素数量
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) {
// newSize比原先size大,全部数据都置为最新值,cache置为全部值,oldCache清空,当前size更新
this.cache = new Map(items);
this.oldCache = new Map();
this._size = items.length;
} else {
// newSize比原先小
if (removeCount > 0) {
// removeCount 个元素需要移除,触发元素清除回调
this._emitEvictions(items.slice(0, removeCount));
}
// 移除 removeCount个元素,并重新赋值给 oldCache
this.oldCache = new Map(items.slice(removeCount));
// cache重新赋值
this.cache = new Map();
// 当前元素统计置为 0 ,
this._size = 0;
}
this.maxSize = newSize;
}
// 返回迭代器对象,顺序为oldCache -> cache
* _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;
}
}
}
// cache中的元素都触发 onEviction 函数
_emitEvictions(cache) {
if (typeof this.onEviction !== 'function') {
return;
}
for (const [key, item] of cache) {
this.onEviction(key, item.value);
}
}
Symbol.iterator
quick-lru中重写该默认迭代器对象,供for...of 使用。参考链接:Symbol.iterator
* [Symbol.iterator]() {
// 重写为读取 cache -> oldCache 的顺序,并返回 [key, value] 的结构
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];
}
}
}
}
keys
* keys() {
for (const [key] of this) {
// 这里用调用上述 Symbol.iterator
yield key;
}
}
values
* values() {
for (const [, value] of this) {
// 这里用调用上述 Symbol.iterator
yield value;
}
}
知识补给站
Generator
- 概念:生成器对象,返回值数据类型为Generator迭代器对象
- 例子
function* gen() { yield 1; yield 2; yield 3; } let g = gen(); console.log(g); // Generator对象 // 获取值需通过 next 函数来操作 console.log(g.next()); // { "value": 1, "done": false } console.log(g.next()); // { "value": 2, "done": false } console.log(g.next()); // { "value": 3, "done": false } console.log(g.next()); // { "value": undefined, "done": true }
总结
通过这次的源码共读,学习到的几点:
- 初步了解到什么是LRU算法,以及如何简单实现
- 了解到生成器Generator的一些基础知识
- 如何去重写默认的Symbol.iterator,输出自己想要的信息