import { Emitter, Event } from './event'; export interface ILRULinkListNode { key: K | undefined; next: ILRULinkListNode | undefined; prev: ILRULinkListNode | undefined; } /** * 自带LRU清理的Map * 双向链表 + Map */ export class LRUMap extends Map { private _onDidDelete = new Emitter<{ key: K; value: V }>(); public readonly onDidDelete: Event<{ key: K; value: V }> = this._onDidDelete.event; public readonly onKeyDidDelete = (key: K, ...args: Parameters>) => Event.filter(this.onDidDelete, (e) => e.key === key)(...args); private head: ILRULinkListNode = { key: undefined, prev: undefined, next: undefined }; private tail: ILRULinkListNode = { key: undefined, prev: undefined, next: undefined }; private map: Map> = new Map(); constructor(private hardLimit: number, private softLimit: number) { super(); if (hardLimit < softLimit) { throw new Error('hardLimit must be greater equal than softLimit.'); } this.head.next = this.tail; this.tail.prev = this.head; } private markRecentUsed(key: K) { if (!this.map.get(key)) { this.map.set(key, { key, prev: undefined, next: undefined }); } this.putHead(this.map.get(key)!); } get(key: K): V | undefined { const v = super.get(key); if (v) { this.markRecentUsed(key); } return v; } set(key: K, value: V): this { this.markRecentUsed(key); super.set(key, value); if (this.size > this.hardLimit) { this.shrink(); } return this; } putHead(node: ILRULinkListNode) { this.deleteNodeFromList(node); const lastHead = this.head.next; this.head.next = node; node.next = lastHead; node.prev = undefined; if (lastHead) { lastHead.prev = node; } } protected deleteNodeFromList(node: ILRULinkListNode) { if (node.prev) { node.prev.next = node.next; } if (node.next) { node.next.prev = node.prev; } } delete(key: K) { const node = this.map.get(key); if (node) { this.deleteNodeFromList(node); } const value = super.get(key); this._onDidDelete.fire({ key, value: value!, }); return super.delete(key); } shrink() { const toDelete = this.size - this.softLimit; let toDeleteNode: ILRULinkListNode = this.tail; for (let i = 0; i < toDelete; i++) { toDeleteNode = this.tail.prev!; if (!toDeleteNode || toDeleteNode === this.head) { break; } else { this.delete(toDeleteNode.key!); } } } } const NOW = Symbol('now'); /** * 支持过期时间 */ export class StaleLRUMap extends LRUMap { /** * * @param hardLimit * @param softLimit * @param maxAge the unit is milliseconds */ constructor(hardLimit: number, softLimit: number, private maxAge: number) { super(hardLimit, softLimit); } get(key: K): V | undefined { const v = super.get(key); if (v) { if (!this._isStale(v)) { return v; } // 过期则删除该条记录 this.delete(key); } return undefined; } set(key: K, value: V): this { value[NOW] = Date.now(); return super.set(key, value); } private _isStale(value: V): boolean { return value[NOW] + this.maxAge <= Date.now(); } }