lru-caching
Version:
A high-performance LRU caching implementation with TTL support, batch operations and memory optimization
864 lines (770 loc) • 29.9 kB
text/typescript
/**
* ========================== 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();