不能直接用std::string::replace或find,因时间复杂度O(n×m)高、无法处理重叠嵌套匹配、易漏替重复替、触发频繁内存重分配且不支持UTF-8码点级操作;必须用AC自动机构建Trie+失败指针,单次扫描实现O(n+m+z)高效匹配与安全掩码。

直接用 std::string::replace 或循环调用 std::string::find 做敏感词脱敏,线上一跑就卡顿、漏匹配、甚至崩溃——这不是配置问题,是算法选错了。AC 自动机(Aho-Corasick)才是唯一能兼顾正确性与吞吐量的解法,其他都是临时凑合。
为什么 std::string::find + replace 必然失败
常见错误现象:std::string::find 每次从头扫,1000 个词 × 10KB 文本 → 实测耗时 8–12ms;更糟的是,“北京”和“北京市”共存时,先替“北京”会把“北京市”切碎,后续再也匹配不到;“张三丰”里“张三”和“三丰”重叠,暴力替换后变成“**丰”,而非预期的“***”。
- 时间复杂度是 O(n × m),n 是文本长度,m 是词表大小,不随业务增长而稳定
- 每次
replace都触发内存 realloc,长文本下拷贝开销爆炸 - UTF-8 中文被当字节处理:
s[3]可能落在某个汉字中间,导致后续 decode 失败 - 没有失败跳转机制,无法支持“最长前缀优先”或“多词同位置命中”
AC 自动机必须这样初始化才不出错
构建阶段出错,后面全白忙。实测 90% 的线上故障源于 fail 指针未正确 BFS 初始化,或敏感词未归一化。
- 敏感词插入前必须去重:
std::set<:string></:string>或std::unordered_set过滤重复项 - 按长度升序插入:短词(如“色”)要先于长词(如“色情”)进 Trie,否则 fail 指针无法指向正确输出
- UTF-8 文本必须做码点级切分:不能直接用
char构建节点;推荐用utf8cpp::utf8to32转为std::u32string后再建树,或用icu::UnicodeString归一化(NFKC) - 构建完成后立即调用
build_failure_links(),别懒、别延迟到第一次 search 时再算
匹配结果怎么安全转成掩码字符串
拿到 std::vector<:pair size_t>></:pair>(起始字节偏移 + 长度)只是开始。直接从左到右 replace 会因索引偏移错乱,导致越界或漏掩。
立即学习“C++免费学习笔记(深入)”;
- 必须按起始偏移倒序排序:
std::sort(matches.rbegin(), matches.rend()),确保右边替换不影响左边坐标 - 掩码字符数必须严格等于原词字节数:用
std::string(len, '*'),别硬写"***"—— 中文占 3 字节,“宝”得替成"***",不是"*" - 结果字符串要预分配:
result.reserve(input.size() + 2 * matches.size()),避免多次realloc - 禁止在循环里拼接:
result += segment是性能杀手;改用result.append(segment)或std::copy到预分配缓冲区
中文、emoji、HTML 混合文本怎么不崩
真实日志/评论/弹幕永远不是纯文本。跳过非文本区域不是可选项,是保命线。
- 先做轻量 tokenization:维护
in_tag(遇' 开启、<code>'>'关闭)、in_url(匹配"https?://"或含'@'且前后为空格/标点)状态,这些区域完全跳过 AC 匹配 - UTF-8 偏移映射必须保留:归一化(NFKC)后字节长度可能变,需构建原始字节位置 ↔ 归一化后位置的双向映射表,脱敏操作最终落回原始字符串坐标
- emoji 不是“一个字符”:U+1F600 ? 是 4 字节,
s.substr(5, 1)会截断;所有索引操作必须基于utf8::distance(s.begin(), s.end())计算码点数,而非s.size() - 生产环境必须缓存 trie:用
static std::shared_ptr<actrie></actrie>+ 读写锁,避免每次请求重建耗时 150–300ms
最易被忽略的点:AC 自动机只解决“在哪匹配”,不解决“怎么替换才不破坏语义”。空格、标点、换行是否保留,掩码是否等宽,HTML 标签是否闭合——这些都在匹配之后,但决定服务能不能上线。



















