遍历字符串必须用 for _, r := range word 而不是 for i := range word,因为 go 中 string 底层是 utf-8 字节数组,后者遍历字节索引,取 word[i] 会导致中文、emoji 等多字节字符乱码,敏感词匹配失效。

为什么遍历字符串必须用 for _, r := range word 而不是 for i := range word
因为 Go 的 string 底层是 UTF-8 字节数组,for i := range word 遍历的是字节索引,不是字符位置。中文、emoji、全角标点在 UTF-8 中占多个字节(比如“王”是 3 个 byte),直接按字节索引取 word[i] 会得到乱码 rune,导致敏感词路径完全无法匹配。
实操建议:
- 所有敏感词构建、文本扫描、节点跳转,一律用
[]rune或for _, r := range s - 别写
word[i]这类操作;如果非要下标访问,先转rs := []rune(word),再用rs[i] - 测试时务必包含“王八蛋”“♥️”“ABC”(全角)等非 ASCII 输入,否则上线后中文漏词不报错,只静默失效
node.isEnd = true 放错位置会导致误杀率飙升
常见错误是在构建词路径的每一步都设 isEnd = true,例如循环内就赋值,结果“王八蛋”会让“王”“王八”“王八蛋”全部标记为结尾——只要文本出现“王”,就触发拦截。
正确做法只在完整敏感词末端节点设 isEnd:
- 循环完所有
rune后,对最终抵达的节点设node.isEnd = true - 不要在
AddChild里设,也不要每次node = node.Next[r]后就设 - 若需支持前缀敏感(如“王八”和“王八蛋”都算),得加独立字段如
isPrefix bool,不能复用isEnd
匹配时只返回“王八”却漏掉“王八羔子”,本质是没做重叠匹配
DFA 默认一次匹配一个最长词,但真实场景需要找出所有可能命中项:“王八羔子”里,“王八”和“王八羔子”都是敏感词,不能只取前者。
关键破局点在于匹配逻辑不能一碰到 isEnd 就 break 或返回:
- 遇到
isEnd时,记录当前匹配位置和长度,然后把扫描起点 +1,重新进树(即“回溯起点+1”) - 或用双指针:固定
start,让end推进找最长匹配,找到后start++继续 - 简单验证可用
strings.ReplaceAllFunc+Contains,但性能差,高并发必须手写滑动匹配
node.Next[c] 前不判空会 panic
Go 中对 nil map 写入直接 panic,而很多人在 AddChild 或匹配循环里直接写 node.Next[r] = newNode,没检查 node.Next 是否已初始化。
现象是本地小数据不报错,压测时突然崩溃,堆栈指向 node.Next[c] 赋值行。
安全写法只有两种:
- 每次访问前手动判空:
if node.Next == nil { node.Next = make(map[rune]*Node) } - 封装成方法:
func (n *Node) getChild(r rune) *Node和func (n *Node) setChild(r rune, child *Node),内部统一处理nil场景
真正容易被忽略的,是匹配循环里那个看似无害的 node = node.Next[r] ——它前面没判空,等于隐式写了 node.Next[r],而 node.Next 可能还是 nil。这个坑在线上高频文本流里才暴露,且难以复现。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











