UNPKG

lru-caching

Version:

A high-performance LRU caching implementation with TTL support, batch operations and memory optimization

864 lines (770 loc) 29.9 kB
/** * ========================== 1. Entry 相关定义(类型安全与性能基础)========================== * 用数组存储节点 + 枚举索引,替代对象 Entry: * 1. 数组索引访问比对象属性快 3-5 倍(无哈希计算/原型链开销) * 2. 枚举消除魔法数字,TypeScript 下自动提示,避免索引错误 */ export enum EntryIndex { KEY = 0, // 缓存键(string | number) VALUE = 1, // 缓存值(泛型 T) NEXT = 2, // 下一个节点指针(Entry<T> | null) PREV = 3, // 上一个节点指针(Entry<T> | null) EXPIRE = 4 // 过期时间戳(ms,null 表示永不过期) } /** * 数组型节点类型定义 * @template T - 缓存值的类型 */ export type Entry<T> = [ string | number, // EntryIndex.KEY T, // EntryIndex.VALUE Entry<T> | null, // EntryIndex.NEXT Entry<T> | null, // EntryIndex.PREV number | null // EntryIndex.EXPIRE ]; /** * ========================== 2. 双向链表(缓存顺序管理核心)========================== * 功能:维护缓存节点的访问顺序(最近使用放尾部,最久未使用放头部) * 优化点: * 1. 哨兵节点(_head/_tail)消除边界判断(无需判断节点是否为头/尾) * 2. 局部缓存频繁访问属性(如 tail.prev),减少重复数组索引查找 * 3. 提供 forEach 方法支持外部安全遍历 */ export class LinkedList<T> { /** 哨兵头节点(不存储实际数据,仅用于简化边界判断) */ private _head: Entry<T>; /** 哨兵尾节点(不存储实际数据,仅用于简化边界判断) */ private _tail: Entry<T>; /** 链表长度(缓存节点总数,不含哨兵) */ private _len = 0; constructor() { // 初始化哨兵节点,形成固定结构:_head <-> _tail this._head = ['__head_sentinel__', null as unknown as T, null, null, null]; this._tail = ['__tail_sentinel__', null as unknown as T, null, null, null]; this._head[EntryIndex.NEXT] = this._tail; this._tail[EntryIndex.PREV] = this._head; } /** * 插入节点到链表尾部(标记为“最近使用”) * @param entry - 待插入的节点 */ insertEntry(entry: Entry<T>): void { const tail = this._tail; const tailPrev = tail[EntryIndex.PREV]!; // 缓存尾部前一个节点,仅访问1次 // 指针调整:tailPrev <-> entry <-> tail tailPrev[EntryIndex.NEXT] = entry; entry[EntryIndex.PREV] = tailPrev; entry[EntryIndex.NEXT] = tail; tail[EntryIndex.PREV] = entry; this._len++; } /** * 从链表中移除指定节点 * @param entry - 待移除的节点 */ remove(entry: Entry<T>): void { // 缓存节点的前后指针,减少重复访问 const entryPrev = entry[EntryIndex.PREV]!; const entryNext = entry[EntryIndex.NEXT]!; // 调整前后节点的指针,跳过当前节点 entryPrev[EntryIndex.NEXT] = entryNext; entryNext[EntryIndex.PREV] = entryPrev; // 重置当前节点指针,便于后续复用 entry[EntryIndex.NEXT] = entry[EntryIndex.PREV] = null; this._len--; } /** * 将节点移动到链表尾部(标记为“最近使用”) * @param entry - 待移动的节点 */ moveToTail(entry: Entry<T>): void { const tail = this._tail; if (entry[EntryIndex.NEXT] === tail) return; // 已在尾部,直接返回 // 步骤1:从原位置移除节点 const entryPrev = entry[EntryIndex.PREV]!; const entryNext = entry[EntryIndex.NEXT]!; entryPrev[EntryIndex.NEXT] = entryNext; entryNext[EntryIndex.PREV] = entryPrev; // 步骤2:插入到尾部 const tailPrev = tail[EntryIndex.PREV]!; tailPrev[EntryIndex.NEXT] = entry; entry[EntryIndex.PREV] = tailPrev; entry[EntryIndex.NEXT] = tail; tail[EntryIndex.PREV] = entry; } /** * 获取“最久未使用”的节点(头哨兵的下一个节点) * @returns 最久未使用的节点,链表为空时返回 null */ getLRUEntry(): Entry<T> | null { const headNext = this._head[EntryIndex.NEXT]; return headNext !== this._tail ? headNext : null; } /** * 获取链表长度(缓存节点总数) * @returns 链表长度 */ len(): number { return this._len; } /** * 清空链表(仅保留哨兵节点) */ clear(): void { this._head[EntryIndex.NEXT] = this._tail; this._tail[EntryIndex.PREV] = this._head; this._len = 0; } /** * 遍历链表所有节点(安全遍历,避免修改节点导致的循环错误) * @param cb - 遍历回调(参数为当前节点) */ forEach(cb: (entry: Entry<T>) => void): void { let current = this._head[EntryIndex.NEXT]; while (current !== this._tail) { const next = current[EntryIndex.NEXT]; // 提前缓存下一个节点,防止遍历中节点被移除 cb(current); current = next!; } } } /** * ========================== 3. 节点池(内存复用核心)========================== * 功能:复用淘汰的缓存节点,减少对象创建与 GC 开销 * 优化点: * 1. 用 LinkedList 管理池内节点,实现“池内 LRU”(淘汰池内最久未使用的节点) * 2. 池满时不直接丢弃,而是淘汰池内最久未用节点,提升复用率 */ class NodePool<T> { /** 池内节点的存储链表(LRU 策略) */ private _list = new LinkedList<T>(); /** 节点池的最大容量 */ private _maxSize: number; /** * @param maxSize - 节点池最大容量 */ constructor(maxSize: number) { this._maxSize = maxSize; } /** * 从池中获取节点(优先复用最近使用的节点,取链表尾部) * @returns 复用的节点,池为空时返回 null */ acquire(): Entry<T> | null { const entry = this._list.getLRUEntry(); if (entry) { this._list.remove(entry); // 从池中移除,标记为“正在使用” return entry; } return null; } /** * 将节点回收到池中(插入链表尾部,标记为“最近使用”) * @param entry - 待回收的节点(需确保已重置指针) */ release(entry: Entry<T>): void { // 池满时,淘汰池内最久未使用的节点(链表头部) if (this._list.len() >= this._maxSize) { const lruEntry = this._list.getLRUEntry(); if (lruEntry) this._list.remove(lruEntry); } this._list.insertEntry(entry); } /** * 清空节点池 */ clear(): void { this._list.clear(); } /** * 获取当前池内节点数量 * @returns 池内节点数量 */ len(): number { return this._list.len(); } /** * (生产环境建议扩展)获取池的最大容量 * @returns 池的最大容量 */ getMaxSize(): number { return this._maxSize; } /** * (生产环境建议扩展)设置池的最大容量 * @param maxSize - 新的最大容量 */ setMaxSize(maxSize: number): void { this._maxSize = maxSize; } /** * (生产环境建议扩展)获取池内节点链表 * @returns 池内节点链表 */ getList(): LinkedList<T> { return this._list; } } /** * ========================== 4. 统计相关定义(可观测性基础)========================== * 轻量化统计设计: * 1. 批量更新计数器(每 BATCH_SIZE 次操作同步到 total),减少单次自增指令开销 * 2. 区分 get/put 操作的统计维度,支持缓存命中率计算 */ interface LightweightStats { get: { batch: number; // 批量计数器(0 ~ BATCH_SIZE-1) total: number; // get 操作总次数 hit: number; // get 操作命中次数 }; put: { batch: number; // 批量计数器(0 ~ BATCH_SIZE-1) total: number; // put 操作总次数 evict: number; // 淘汰节点总次数 expire: number; // 过期节点总次数 }; } /** * ========================== 5. LRU 主类(对外核心 API)========================== * 核心功能:LRU 缓存策略实现,支持 TTL 过期、批量操作、资源释放回调、统计监控 */ export default class LRU<T> { /** 缓存节点的顺序管理链表 */ private _list = new LinkedList<T>(); /** 键到节点的映射(O(1) 查找) */ private _map = new Map<string | number, Entry<T>>(); /** 缓存的最大容量(节点总数上限) */ private _maxSize: number; /** 节点池(复用淘汰节点) */ private _nodePool: NodePool<T>; /** 节点池容量调整周期(ms) */ private _poolAdjustInterval: number; /** 节点池调整定时器 */ private _adjustTimer: ReturnType<typeof setInterval> | null = null; /** 过期节点检查周期(ms) */ private _expireCheckInterval: number; /** 过期检查定时器 */ private _expireTimer: ReturnType<typeof setInterval> | null = null; /** 节点淘汰时的回调(用于释放资源,如纹理、句柄) */ private _onEvict?: (key: string | number, value: T) => void; /** 轻量化统计数据 */ private _stats: LightweightStats; /** 统计批量更新阈值(每 100 次操作同步到 total) */ private static readonly BATCH_SIZE = 100; /** * 构造函数 * @param maxSize - 缓存最大容量(必须 ≥1) * @param initialPoolRatio - 节点池初始容量占缓存容量的比例(默认 0.5) * @param poolAdjustInterval - 节点池容量调整周期(默认 30000ms = 30s) * @param expireCheckInterval - 过期节点检查周期(默认 5000ms = 5s) * @param onEvict - 节点淘汰时的回调(可选,用于释放资源) */ constructor( maxSize: number, initialPoolRatio = 0.5, poolAdjustInterval = 30000, expireCheckInterval = 5000, onEvict?: (key: string | number, value: T) => void ) { this._maxSize = Math.max(1, maxSize); // 确保容量 ≥1 // 初始化节点池(容量 = 缓存容量 × 初始比例,且 ≥1) const poolMaxSize = Math.max(1, Math.floor(this._maxSize * initialPoolRatio)); this._nodePool = new NodePool<T>(poolMaxSize); this._poolAdjustInterval = poolAdjustInterval; this._expireCheckInterval = expireCheckInterval; this._onEvict = onEvict; // 初始化统计数据 this._stats = { get: { batch: 0, total: 0, hit: 0 }, put: { batch: 0, total: 0, evict: 0, expire: 0 } }; // 启动定时器(节点池调整 + 过期检查) this._startPoolAdjustTimer(); this._startExpireTimer(); } /** * -------------------------- 私有方法:定时器管理 -------------------------- */ /** * 启动节点池容量调整定时器 * 优化逻辑:基于“池复用率”动态调整容量,步长自适应(偏差越大,步长越大) */ private _startPoolAdjustTimer(): void { if (this._adjustTimer) clearInterval(this._adjustTimer); this._adjustTimer = setInterval(() => { const poolLen = this._nodePool.len(); const cacheLen = this._list.len(); if (cacheLen === 0 || poolLen === 0) return; // 缓存/池为空时不调整 // 计算池复用率(池内节点被使用的频率,避免分母为0) const poolUsageRate = this._stats.put.total === 0 ? 0 : Math.min(1, poolLen / this._stats.put.total * 10); // 限制在 0~1 之间 const targetUsageRate = 0.5; // 目标复用率(50%) const usageDiff = poolUsageRate - targetUsageRate; // 复用率偏差 // 自适应步长:偏差越大,步长越大(最大 3,避免容量波动过大) const step = Math.min(3, Math.max(1, Math.abs(Math.floor(usageDiff * 5)))); const currentPoolMaxSize = this._nodePool.getMaxSize(); // 调整池容量 if (poolUsageRate > 0.7) { // 复用率过高(>70%):扩大池容量(最大不超过缓存容量的 90%) const newMaxSize = Math.min(Math.floor(this._maxSize * 0.9), currentPoolMaxSize + step); this._nodePool.setMaxSize(newMaxSize); } else if (poolUsageRate < 0.3) { // 复用率过低(<30%):缩小池容量(最小 ≥1) const newMaxSize = Math.max(1, currentPoolMaxSize - step); this._nodePool.setMaxSize(newMaxSize); // 缩小后若池内节点超量,淘汰多余节点 while (this._nodePool.len() > newMaxSize) { const lruEntry = this._nodePool.getList().getLRUEntry(); if (lruEntry) this._nodePool.getList().remove(lruEntry); } } }, this._poolAdjustInterval); } /** * 启动过期节点检查定时器 * 优化逻辑:批量收集过期节点 → 批量清理,减少重复的 remove/delete 调用 */ private _startExpireTimer(): void { if (this._expireTimer) clearInterval(this._expireTimer); this._expireTimer = setInterval(() => { const now = Date.now(); const expiredEntries: Entry<T>[] = []; // 步骤1:批量收集所有过期节点(单次遍历,避免多次循环) this._list.forEach(entry => { const expireTime = entry[EntryIndex.EXPIRE]; if (expireTime && expireTime < now) { expiredEntries.push(entry); } }); // 步骤2:批量清理过期节点 expiredEntries.forEach(entry => { const key = entry[EntryIndex.KEY]; const value = entry[EntryIndex.VALUE]; // 从缓存中移除 this._list.remove(entry); this._map.delete(key); // 触发淘汰回调(释放资源) this._onEvict?.(key, value); // 回收到节点池 this._nodePool.release(entry); // 更新统计 this._stats.put.batch++; if (this._stats.put.batch >= LRU.BATCH_SIZE) { this._stats.put.total += this._stats.put.batch; this._stats.put.expire += this._stats.put.batch; this._stats.put.batch = 0; } else { this._stats.put.expire++; } }); // 补全剩余统计(不足 BATCH_SIZE 的部分) if (this._stats.put.batch > 0) { this._stats.put.total += this._stats.put.batch; this._stats.put.batch = 0; } }, this._expireCheckInterval); } /** * -------------------------- 私有方法:节点管理 -------------------------- */ /** * 获取节点(优先从池复用,无则新建) * @param val - 缓存值 * @param key - 缓存键 * @param ttl - 过期时间(ms,可选) * @returns 可用的节点 */ private _acquireEntry(val: T, key: string | number, ttl?: number): Entry<T> { this._stats.put.total++; // 累计池请求次数(用于计算复用率) // 1. 从节点池复用 const pooledEntry = this._nodePool.acquire(); if (pooledEntry) { // 仅重置必要属性(next/prev 已在 release 时重置) pooledEntry[EntryIndex.KEY] = key; pooledEntry[EntryIndex.VALUE] = val; pooledEntry[EntryIndex.EXPIRE] = ttl ? Date.now() + ttl : null; return pooledEntry; } // 2. 新建节点(数组初始化,性能最优) return [ key, val, null, null, ttl ? Date.now() + ttl : null ]; } /** * -------------------------- 公有方法:核心缓存操作 -------------------------- */ /** * 插入/更新缓存 * @param key - 缓存键 * @param value - 缓存值 * @param ttl - 过期时间(ms,可选,null 表示永不过期) * @returns 被淘汰的节点值(缓存满时),无淘汰则返回 null */ put(key: string | number, value: T, ttl?: number): T | null { // 统计批量更新 this._stats.put.batch++; const isBatchFull = this._stats.put.batch >= LRU.BATCH_SIZE; if (isBatchFull) { this._stats.put.total += this._stats.put.batch; this._stats.put.batch = 0; } const map = this._map; const list = this._list; const tail = list['_tail']; // 缓存尾部节点,减少重复访问 let removed: T | null = null; // 情况1:键已存在 → 更新值和过期时间 const existing = map.get(key); if (existing) { existing[EntryIndex.VALUE] = value; existing[EntryIndex.EXPIRE] = ttl ? Date.now() + ttl : null; // 不在尾部则移动到尾部(标记为最近使用) if (existing[EntryIndex.NEXT] !== tail) { list.moveToTail(existing); } return null; } // 情况2:缓存容量=1 → 特殊处理(直接淘汰当前节点) if (this._maxSize === 1) { const currentEntry = list.getLRUEntry(); if (currentEntry) { list.remove(currentEntry); map.delete(currentEntry[EntryIndex.KEY]); removed = currentEntry[EntryIndex.VALUE]; this._onEvict?.(currentEntry[EntryIndex.KEY], removed); this._nodePool.release(currentEntry); this._stats.put.evict++; // 统计淘汰次数 } } // 情况3:缓存满 → 淘汰最久未使用节点(常规逻辑) else if (list.len() >= this._maxSize) { const lruEntry = list.getLRUEntry(); if (lruEntry) { list.remove(lruEntry); map.delete(lruEntry[EntryIndex.KEY]); removed = lruEntry[EntryIndex.VALUE]; this._onEvict?.(lruEntry[EntryIndex.KEY], removed); this._nodePool.release(lruEntry); this._stats.put.evict++; // 统计淘汰次数 } } // 插入新节点 const newEntry = this._acquireEntry(value, key, ttl); list.insertEntry(newEntry); map.set(key, newEntry); // 补全统计(淘汰次数批量同步) if (isBatchFull) { this._stats.put.evict = Math.floor(this._stats.put.evict / LRU.BATCH_SIZE) * LRU.BATCH_SIZE; } return removed; } /** * 获取缓存值 * @param key - 缓存键 * @returns 缓存值(存在且未过期),否则返回 undefined */ get(key: string | number): T | undefined { // 统计批量更新 this._stats.get.batch++; const isBatchFull = this._stats.get.batch >= LRU.BATCH_SIZE; if (isBatchFull) { this._stats.get.total += this._stats.get.batch; this._stats.get.batch = 0; } // 情况1:键不存在 → 返回 undefined const entry = this._map.get(key); if (!entry) { if (isBatchFull) { this._stats.get.total += this._stats.get.batch; this._stats.get.batch = 0; } return undefined; } // 情况2:节点已过期 → 淘汰并返回 undefined const now = Date.now(); const expireTime = entry[EntryIndex.EXPIRE]; if (expireTime && expireTime < now) { this._list.remove(entry); this._map.delete(key); this._nodePool.release(entry); this._stats.put.expire++; // 统计过期次数 if (isBatchFull) { this._stats.get.total += this._stats.get.batch; this._stats.get.batch = 0; } return undefined; } // 情况3:正常命中 → 移动到尾部并返回值 this._stats.get.hit++; // 统计命中次数 this._list.moveToTail(entry); // 补全统计 if (isBatchFull) { this._stats.get.total += this._stats.get.batch; this._stats.get.hit = Math.floor(this._stats.get.hit / LRU.BATCH_SIZE) * LRU.BATCH_SIZE; this._stats.get.batch = 0; } return entry[EntryIndex.VALUE]; } /** * 删除指定缓存键 * @param key - 缓存键 * @returns 被删除的缓存值,键不存在则返回 null */ delete(key: string | number): T | null { const entry = this._map.get(key); if (!entry) return null; // 从缓存中移除 this._list.remove(entry); this._map.delete(key); const removedValue = entry[EntryIndex.VALUE]; // 触发淘汰回调 this._onEvict?.(key, removedValue); // 回收到节点池 this._nodePool.release(entry); return removedValue; } /** * -------------------------- 公有方法:批量操作 -------------------------- */ /** * 批量插入/更新缓存 * @param entries - 批量缓存数据(数组,每个元素含 key/value/ttl) * @returns 被淘汰的缓存值数组 */ batchPut(entries: Array<{ key: string | number; value: T; ttl?: number }>): T[] { const removedValues: T[] = []; const map = this._map; const list = this._list; const maxSize = this._maxSize; const now = Date.now(); // 步骤1:批量处理已存在的键(更新值和过期时间) entries.forEach(item => { const { key, value, ttl } = item; const existing = map.get(key); if (existing) { existing[EntryIndex.VALUE] = value; existing[EntryIndex.EXPIRE] = ttl ? now + ttl : null; if (existing[EntryIndex.NEXT] !== list['_tail']) { list.moveToTail(existing); } } }); // 步骤2:过滤需要插入的新键(排除已存在的键) const newEntries = entries.filter(item => !map.has(item.key)); if (newEntries.length === 0) return removedValues; // 步骤3:计算需要淘汰的节点数量(新插入数量 + 当前容量 - 最大容量) const needEvictCount = Math.max(0, newEntries.length + list.len() - maxSize); if (needEvictCount > 0) { // 批量淘汰最久未使用节点 let evictCount = 0; let current = list.getLRUEntry(); while (current && evictCount < needEvictCount) { const next = current[EntryIndex.NEXT]; const key = current[EntryIndex.KEY]; const value = current[EntryIndex.VALUE]; // 移除节点 list.remove(current); map.delete(key); this._onEvict?.(key, value); this._nodePool.release(current); // 收集被淘汰的值 removedValues.push(value); this._stats.put.evict++; evictCount++; current = next !== list['_tail'] ? next : null; } } // 步骤4:批量插入新节点 newEntries.forEach(item => { const { key, value, ttl } = item; const newEntry = this._acquireEntry(value, key, ttl); list.insertEntry(newEntry); map.set(key, newEntry); }); // 步骤5:更新统计 this._stats.put.batch += entries.length; if (this._stats.put.batch >= LRU.BATCH_SIZE) { this._stats.put.total += this._stats.put.batch; this._stats.put.evict = Math.floor(this._stats.put.evict / LRU.BATCH_SIZE) * LRU.BATCH_SIZE; this._stats.put.batch = 0; } return removedValues; } /** * 批量删除缓存键 * @param keys - 待删除的缓存键数组 * @returns 被删除的缓存值数组 */ batchDelete(keys: (string | number)[]): T[] { const removedValues: T[] = []; const map = this._map; const list = this._list; keys.forEach(key => { const entry = map.get(key); if (entry) { list.remove(entry); map.delete(key); const value = entry[EntryIndex.VALUE]; removedValues.push(value); this._onEvict?.(key, value); this._nodePool.release(entry); } }); return removedValues; } /** * -------------------------- 公有方法:辅助操作 -------------------------- */ /** * 遍历所有缓存节点(安全遍历,支持读取键/值/过期时间) * @param cb - 遍历回调(参数:key, value, expireTime) */ forEach(cb: (key: string | number, value: T, expireTime?: number) => void): void { this._list.forEach(entry => { const key = entry[EntryIndex.KEY]; const value = entry[EntryIndex.VALUE]; const expireTime = entry[EntryIndex.EXPIRE]; cb(key, value, expireTime || undefined); }); } /** * 清空所有缓存 */ clear(): void { // 批量回收节点到池(不超过池最大容量) this._list.forEach(entry => { if (this._nodePool.len() < this._nodePool.getMaxSize()) { this._nodePool.release(entry); } }); // 清空核心结构 this._list.clear(); this._map.clear(); this._nodePool.clear(); // 重置统计数据 this._stats = { get: { batch: 0, total: 0, hit: 0 }, put: { batch: 0, total: 0, evict: 0, expire: 0 } }; } /** * 获取当前缓存容量(节点总数) * @returns 缓存容量 */ len(): number { return this._list.len(); } /** * 检查缓存是否包含指定键(并判断是否过期) * @param key - 缓存键 * @returns 存在且未过期返回 true,否则返回 false */ has(key: string | number): boolean { const entry = this._map.get(key); if (!entry) return false; // 检查是否过期 const expireTime = entry[EntryIndex.EXPIRE]; return !expireTime || expireTime >= Date.now(); } /** * 获取缓存统计信息(命中率、操作次数、节点池状态) * @returns 统计信息对象 */ getStats(): Readonly<{ get: { total: number; hit: number; hitRate: number }; // get 操作统计(含命中率) put: { total: number; evict: number; expire: number }; // put 操作统计 pool: { size: number; maxSize: number }; // 节点池状态 }> { // 补全批量统计的余数(确保统计准确) const getTotal = this._stats.get.total + this._stats.get.batch; const getHit = this._stats.get.hit + (this._stats.get.batch > 0 ? Math.min(this._stats.get.batch, this._stats.get.hit % LRU.BATCH_SIZE) : 0); const putTotal = this._stats.put.total + this._stats.put.batch; const putEvict = this._stats.put.evict + (this._stats.put.batch > 0 ? Math.min(this._stats.put.batch, this._stats.put.evict % LRU.BATCH_SIZE) : 0); const putExpire = this._stats.put.expire + (this._stats.put.batch > 0 ? Math.min(this._stats.put.batch, this._stats.put.expire % LRU.BATCH_SIZE) : 0); return { get: { total: getTotal, hit: getHit, hitRate: getTotal === 0 ? 0 : Math.round((getHit / getTotal) * 10000) / 100 // 命中率保留两位小数 }, put: { total: putTotal, evict: putEvict, expire: putExpire }, pool: { size: this._nodePool.len(), maxSize: this._nodePool.getMaxSize() } }; } /** * 销毁缓存(清理定时器、释放内存,避免内存泄漏) */ dispose(): void { // 清理定时器 if (this._adjustTimer) { clearInterval(this._adjustTimer); this._adjustTimer = null; } if (this._expireTimer) { clearInterval(this._expireTimer); this._expireTimer = null; } // 清空所有资源,帮助 GC 回收 this.clear(); (this._list as unknown) = null; (this._map as unknown) = null; (this._nodePool as unknown) = null; this._onEvict = undefined; } } /** * ========================== 6. 使用示例 =========================== * 覆盖常见场景:基础使用、批量操作、资源释放、统计监控 */ function test(){ // 示例1:基础缓存(无过期时间) const basicLRU = new LRU<string>(10); basicLRU.put('name', 'zrender'); console.log(basicLRU.get('name')); // 输出:zrender console.log(basicLRU.has('name')); // 输出:true // 示例2:带 TTL 过期的缓存(5秒过期) const ttlLRU = new LRU<number>(5); ttlLRU.put('count', 100, 5000); // 5秒后过期 setTimeout(() => { console.log(ttlLRU.get('count')); // 5秒后输出:undefined }, 6000); // 示例3:资源型缓存(淘汰时释放 WebGL 纹理) const textureLRU = new LRU<WebGLTexture>(3, 0.5, 30000, 5000, (key, texture) => { // 淘汰时释放纹理资源 // const gl = getWebGLContext(); // 需自行实现获取 WebGL 上下文的方法 // gl.deleteTexture(texture); }); // 示例4:批量操作 const batchLRU = new LRU<number>(10); // 批量插入 const removed = batchLRU.batchPut([ { key: 'a', value: 1 }, { key: 'b', value: 2, ttl: 10000 }, { key: 'c', value: 3 } ]); // 批量删除 const deleted = batchLRU.batchDelete(['a', 'b']); // 示例5:统计监控 setInterval(() => { const stats = batchLRU.getStats(); console.log(`缓存命中率:${stats.get.hitRate}%,淘汰次数:${stats.put.evict}`); }, 1000); } //test();