敏感词匹配必须统一使用[]rune而非[]byte,因中文、emoji等utf-8字符非单字节,[]byte遍历会破坏编码导致匹配失败;isend标志须在完整rune路径终点设置,避免误标前缀;需支持重叠匹配、正确处理nil map及ac自动机失败指针传播。

敏感词匹配必须用 []rune,别碰 []byte
中文、emoji、生僻字在 Go 里不是单字节,[]byte 遍历会切碎 UTF-8 编码。比如“王八蛋”被拆成 [229 165 172 229 165 189 ...],树建对了也查不到——因为节点 key 是 rune,而你拿 byte 去查,永远不命中。
所有环节统一转 []rune:
- 插入敏感词前:
for _, r := range []rune(word),不是for i := range word - 扫描文本时:
chars := []rune(text),再遍历chars - 如果真要走
[]byte(如纯 ASCII 日志),必须提前校验:if !utf8.ValidString(text) { return err }
isEnd = true 必须在循环结束后赋值
这是最隐蔽的误杀源头。错误写法:for _, r := range word { node = node.getChild(r); node.isEnd = true }——这会让“王”“王八”“王八蛋”全被标为结尾,导致用户输“王者”就被拦截。
正确逻辑只在完整路径终点设标志:
- 构建时:先走完全部
rune,最后一步的node.isEnd = true - 如需支持“前缀敏感”(“王八”和“王八羔子”都算违规),必须新增字段如
isPrefix bool,不能复用isEnd
必须支持重叠匹配,不能一命就停
输入“王八羔子”,只返回“王八”就结束?那是 demo 级实现。生产环境必须覆盖所有可能起点:
- 双指针是简单可靠的方案:
start固定,end推进;每次命中isEnd就记录位置,然后start++,重新进树 - 别依赖
strings.ReplaceAllFunc:它内部单次扫描、不支持重叠,且性能差一个数量级 - 如果用 AC 自动机,失败指针必须正确传播
isEnd(即“输出函数”需合并祖先节点的终结标记)
node.children[char] panic?90% 没判 nil
本地小数据测不出,压测时突然报 assignment to entry in nil map,堆栈指向 AddChild 或匹配循环里的 node.children[char]——这就是 node.children 还是 nil,却直接当 map 用了。
每一处访问或写入前都得检查:
- 暴力写法:
if node.children == nil { node.children = make(map[rune]*Node) } - 更稳妥的是封装方法:
func (n *Node) setChild(r rune, child *Node),内部统一判空 - 别信“初始化时已经过”——树越深,子节点的子节点越容易漏初始化
真正难的不是建树,而是让每个 rune 走对路、每个 map 不 panic、每个起点都不被跳过。这些点线上一出问题,就是漏词或误杀,没有中间态。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











