c++oding="utf-8" ?>
敏感词匹配不能用std::string::find逐个遍历,因时间复杂度高达o(n×m×l),线上服务不可承受;应使用ac自动机等预处理结构,将复杂度降至o(n+k),并借助aho-corasick或hyperscan等成熟库实现。

敏感词匹配为什么不能用 std::string::find 逐个遍历?
因为效率太低。假设有 1 万个敏感词,每个待检测文本平均长度 200 字符,暴力调用 std::string::find 每次都要从头扫一遍,最坏时间复杂度接近 O(N × M × L)(N=文本长,M=词表大小,L=平均词长),线上服务扛不住。
真正可行的路径是构建一次性预处理结构:AC 自动机(Aho-Corasick)是工业级首选——它把所有敏感词构建成一棵带失败指针的 Trie 树,单次扫描文本即可完成全部匹配,时间复杂度降到 O(N + K)(K=匹配到的词总数)。
- 别自己手写 AC 自动机:用现成库更稳,比如
aho-corasick(C++17+,header-only)或hyperscan(Intel 优化,适合超大词表) - 如果词表固定且较小(double-array-trie 库),内存更省、构建快
- 避免在运行时动态增删敏感词——AC 自动机重建开销大;如需热更新,得配合版本切换或增量编译机制
如何用 aho-corasick 实现屏蔽与替换?
这个库不直接提供“替换”接口,得自己组合匹配结果做字符串切片。核心逻辑是:拿到所有匹配起始/结束位置后,按逆序处理(防止索引偏移),逐段拼接新字符串。
#include <ahocorasick.hpp>
#include <string>
#include <vector><p>std::string replace_sensitive(const std::string& text,
const std::vector<:string>& keywords,
const std::string& replacement = "***") {
ac::Automaton<char> ac;
for (const auto& kw : keywords) ac.add(kw);
ac.build();</char></:string></p>
<pre class="brush:php;toolbar:false;">std::vector<std::pair<size_t, size_t>> matches;
ac.search(text, [&](size_t pos, size_t len) {
matches.emplace_back(pos, pos + len);
});
// 逆序处理,避免下标错乱
std::string result;
size_t last = text.length();
for (auto it = matches.rbegin(); it != matches.rend(); ++it) {
result = text.substr(it->second, last - it->second) + replacement + result;
last = it->first;
}
result = text.substr(0, last) + result;
return result;
}
-
ac.search的回调里只存位置,别在回调里做字符串操作——否则多次拷贝拖慢性能 - 中文敏感词必须确保编码一致:输入文本、词表都用 UTF-8,且库支持多字节字符(
aho-corasick默认按字节处理,对 UTF-8 安全) - 若需高亮而非替换(如加
<span class="blocked">xxx</span>),同样用位置信息插入 HTML 片段,但要注意转义原始文本
为什么正则表达式(std::regex)不适合敏感词引擎?
std::regex 在 C++11/14 中实现质量差、性能不可控,GCC libstdc++ 的 std::regex 甚至存在栈溢出风险;Clang libc++ 虽好些,但构造 1 万条规则的正则("word1|word2|...")会导致编译态爆炸和运行时回溯失控。
- 即使改用
PCRE2或RE2,单正则匹配仍不如 AC 自动机稳定——后者无回溯、确定性 O(N),前者最坏仍是指数级 - 正则难以支持“最长匹配优先”或“重叠匹配”等敏感词常见需求(比如“法轮功”和“轮功”同时存在时,该匹配哪个?AC 可设优先级或返回全部)
- 调试困难:
std::regex错误常报std::regex_error附带模糊码,而 AC 构建失败会明确提示哪个词非法(如含空串、控制字符)
上线前必须验证的三个边界点
很多线上事故不是算法错,而是边界没压住:
- 空字符串或全是空白符的输入:AC 自动机能正常处理,但你的替换逻辑可能崩溃(
substr(0, 0)没问题,但substr(n, m)当n > text.size()就 UB) - 超长关键词(如 500 字符):Trie 构建可能吃光栈空间,建议预检
keyword.length() - 敏感词包含正则元字符(
.、*、?):AC 自动机视其为普通字符,但如果你混用了正则做二次过滤,就会语义错乱——统一走字面匹配,别掺杂
真正麻烦的是词表维护本身:同音词、形近字、拆字(“和-谐”)、拼音缩写(“hx”)需要额外规则层,这部分没法靠引擎自动解决,得在加载词表时就做归一化预处理。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











