不能用 strings.ReplaceAll 处理大文本,因会将整个文件加载到内存并多次拷贝字符串,GC压力大且需重复扫描;应采用流式处理结合 ahocorasick 多模式匹配算法,预加载敏感词构建自动机,按行或分块读取并精准替换。

为什么不能用 strings.ReplaceAll 处理大文本
内存会爆,GC 压力大,性能差一个数量级。对 500MB 日志调用 strings.ReplaceAll,不仅把整个文件加载进内存,每次替换还生成新字符串副本(Go 字符串不可变),几百个敏感词就得扫几百遍。真正可行的路只有一条:流式处理 + 多模式匹配。
必须用 []rune,别碰 []byte
中文、emoji、生僻字在 UTF-8 下占多个字节,[]byte 遍历会切碎字符。比如“王八蛋”被拆成三个非法字节单元,建树正确但匹配失败。所有环节统一转 []rune:
- 插入敏感词时:
for _, r := range []rune(word),不是for i := range word - 扫描文本时:
chars := []rune(text),再for i, r := range chars - 若硬要优化纯 ASCII 场景,必须先校验:
if !utf8.ValidString(text) { return err }
isEnd = true 必须在循环结束后赋值
常见错误是在 Trie 构建循环里每走一步都设 node.isEnd = true,导致“王”“王八”“王八蛋”全被标为结尾,用户输“王者”也被拦截。正确做法是:
- 遍历完所有
rune后,在最终节点才赋值node.isEnd = true - 如需支持前缀敏感(“王八”和“王八羔子”都算),新增字段如
isPrefix bool,不能复用isEnd - 匹配逻辑不能一碰到
isEnd == true就 return —— 要支持重叠匹配,比如“王八羔子”得同时命中“王八”和“王八羔子”
node.children 访问前必须判 nil
本地小数据测不出,压测或热更新时突然 panic:assignment to entry in nil map,堆栈指向 node.children[char]。原因:子节点的子节点可能没初始化。解决方式:
立即学习“go语言免费学习笔记(深入)”;
- 每次访问前手动检查:
if node.children == nil { node.children = make(map[rune]*Node) } - 更稳妥的是封装方法:
func (n *Node) getChild(r rune) *Node和func (n *Node) setChild(r rune, child *Node),内部统一判空 - 并发加载、配置变更、热更新都会让某分支
children仍为 nil,不能依赖“一次初始化就万事大吉”
真正难的不是建树,是让树在高并发、中英文混排、emoji 穿插、热更新频繁的线上环境里不 panic、不漏词、不误杀 —— 这些边界条件,比算法本身更消耗精力。


















