Set不能直接做滑动窗口,因其无过期机制、不维护插入顺序、无限增长且多标签页不同步;正确方案是用LRUMap存储指纹与时间戳,配合BroadcastChannel同步。

Set 本身不适合直接当滑动窗口缓存用——它没有内置的过期机制、访问顺序管理或容量限制,无法自动淘汰旧消息。WebSocket 消息去重要求“按时间窗口过滤重复指纹”,而 Set 只管唯一性,不管生命周期。
为什么不能直接用 Set 做滑动窗口?
Set 的核心能力是去重和 O(1) 查找,但它:
- 不记录插入时间,无法判断某条消息是否已超时(比如 30 秒幂等窗口)
- 不维护访问顺序,没法实现“最近最少使用”淘汰逻辑
- 无限增长,高频场景下(如弹幕、协作编辑)内存持续上涨,GC 压力大
- 多标签页间无法同步,localStorage 都比它强一点(至少能持久)
正确做法:用 LRUMap + Set 思路做底层,但不用原生 Set
你可以借鉴 Set 的“唯一性校验”思想,但必须换一个带控制能力的数据结构。推荐组合方案:
-
主缓存用 LRUMap:键为消息指纹(
${msg.common.msgId}_${msg.traceId}),值为到达时间戳;设容量上限(如 1000 条),自动淘汰最久未用项 -
去重判断仍用 has() 语义:和 Set 的
.has(key)一样简洁,只是底层多了 TTL 校验 - 过期检查不依赖定时器:每次 get 时对比当前时间与存储时间,超时则 delete 并返回 false,避免轮询开销
- 多标签页靠 BroadcastChannel 同步删除动作:一个标签页剔除某指纹,广播通知其他页同步清除,弥补 Set 无法共享的短板
如果硬要用 Set,至少得加一层包装
真想保留 Set 接口风格,可以封装一个轻量 wrapper:
- 内部用 Map 存
{ fingerprint: timestamp },用 Set 的.add()语义对外暴露 - 每次 add 前先清理过期项(遍历 Map.keys() 删老数据),再判断是否已存在且未超时
- 手动控制 size 上限,超过就按时间戳删最老的几条(不是自动 LRU,但够用)
- 不推荐长期用,适合 PoC 或低频场景;高可靠业务请直接上 LRUMap 库(如
lru-cache浏览器版或自实现)
不复杂但容易忽略:去重不是“有没有见过”,而是“现在还承不承认它有效”。Set 只回答前半句,后半句得靠时间感知和主动管理。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











