滑动窗口不适用于敏感词过滤,因其无法处理变长词、重叠匹配及中文/emoji的rune单位问题;应采用双指针+trie树遍历,统一用[]rune、严格判空children、正确设置isend。

滑动窗口不是敏感词过滤的主流方案,Go里用它做实时过滤容易漏词、错位、性能反降——真正该用的是 Trie 或 AC自动机,窗口只适合极简场景下的辅助定位。
为什么滑动窗口在敏感词过滤中基本不适用
滑动窗口本质是固定长度子串枚举,而敏感词长度不一、边界模糊、需支持重叠匹配。比如“王八”和“王八羔子”共存时,窗口若设为4,会跳过“王八”;设为6,又无法覆盖“王八羔子”(7字)。更关键的是:窗口无法跳过无效前缀快速回退,遇到“王者荣耀”这种词,必须从每个位置重新试探,时间复杂度退化为 O(n²)。
- 敏感词长度未知且差异大,窗口大小无法兼顾短词与长词
- 中文/emoji 是
[]rune单位,但窗口常按[]byte切分,直接导致“王”被切成 3 字节,匹配永远失败 - 无法利用前缀共享特性,每次滑动都做全量比对,
strings.Contains调用频次爆炸
真正该用的匹配逻辑:双指针 + Trie 树遍历
所谓“滑动”,生产环境实际用的是双指针模拟——start 固定起点,end 向右推进,每步查 Trie 当前节点是否命中 isEnd。命中后记录,start++ 重进树,不依赖窗口大小。
-
start从 0 到len(runes)-1遍历,确保每个字符都作为潜在词首被检查 -
end每次取runes[end]查node.children[r],不存在则 break 并重置 node 到 root - 每次
node.isEnd == true时,记录[start, end]区间,继续推进end(支持“王八”+“王八羔子”同时命中) - 所有
node.children访问前必须判空:if node.children == nil { node.children = make(map[rune]*Node) }
必须统一用 []rune,否则中文全崩
哪怕你代码里写了“滑动”,只要输入含中文、emoji 或生僻字,for i := range text 就是在遍历字节索引,不是字符。一个“王”字占 3 字节,i=0,1,2 分别拿到乱码 byte,根本走不进 Trie 正确路径。
- 插入敏感词前:
for _, r := range []rune(word),不是for i := range word - 扫描文本前:
chars := []rune(text),后续所有循环基于chars索引 - 如果硬要处理纯 ASCII 日志,加校验:
if !utf8.ValidString(text) { return err } -
isEnd = true只能在完整word的最后一个rune对应节点设置,绝不能在循环体内赋值
node.children[char] panic 的真实原因和解法
压测时突然报 panic: assignment to entry in nil map,90% 是某个中间节点的 children 还是 nil,却直接写 node.children[r] = child。树越深,越容易漏初始化——建树时 root 初始化了,但它的子节点、孙子节点未必。
- 别信“我初始化过”,每次访问
node.children前都加判空,或封装成方法:func (n *Node) setChild(r rune, child *Node) - 匹配循环里同样要判:
if node.children == nil || node.children[r] == nil { break } - 用
sync.Pool复用Node实例可减 GC 压力,但不解决判空问题
真正难的不是想出“滑动”这个词,而是让每个 rune 走对路、每个 map 不 panic、每个起点都不被跳过——这些点线上一出问题,就是漏词或误杀,没有中间态。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











