不能直接用 std::unordered_set 做敏感词过滤,因为它仅支持精确匹配,无法进行子串查找;需采用多模式字符串匹配算法,如 ac 自动机——一种带失败指针的 trie 结构,可单次扫描识别所有敏感子串。

为什么不能直接用 std::unordered_set 做敏感词过滤
因为匹配要支持子串查找(比如文本“我爱学习”含敏感词“学习”),而 std::unordered_set 只能做精确键匹配。你往里塞了 "学习",查 "我爱学习" 会返回 false——它根本不会拆解或滑动扫描。
真正要用的是「多模式字符串匹配」,核心是把所有敏感词构建成一个可快速跳转的结构。最轻量又够用的选择是 AC 自动机(Aho-Corasick),它本质是带失败指针的 Trie,单次扫描就能找出所有命中子串。
- 别自己从零手写失败指针逻辑,容易在 border 计算和 BFS 初始化上出错
- 如果敏感词量少(for (const auto& word : words) if (text.find(word) != std::string::npos) {...},但注意
std::string::find平均 O(n×m),文本长时明显卡顿 - 避免用正则(如
std::regex)——编译开销大,且 C++ 标准库 regex 实现普遍慢,还可能栈溢出
AC 自动机的最小可行实现要点
只保留插入、构建、搜索三个接口,不加额外功能(如替换、高亮)。关键不是代码行数,而是指针跳转逻辑不出错。
- 节点结构至少含:
std::array<int> next</int>(ASCII 简化版,-1 表示无边),int fail,bool is_end,int output(命中词索引或长度) - 构建
fail指针必须用 BFS(队列),不能 DFS;初始时根节点所有子节点fail指向根,再逐层推导 - 搜索时每步都要沿
fail链向上检查:即使当前节点不终结,其fail节点可能是终结点——这是 AC 能找全匹配的关键 - 示例片段:
void search(const std::string& text) { int cur = 0; for (char c : text) { while (cur != 0 && nodes[cur].next[static_cast<unsigned char>(c)] == -1) { cur = nodes[cur].fail; } cur = nodes[cur].next[static_cast<unsigned char>(c)]; if (cur == -1) cur = 0; for (int i = cur; i != 0; i = nodes[i].fail) { if (nodes[i].is_end) report(i); } } }</unsigned></unsigned>
哈希在这里起什么作用?其实只用在词表加载阶段
AC 自动机本身不依赖哈希,但你可以用 std::unordered_map<:string int></:string> 把敏感词映射到唯一 ID,这样在 report(i) 时能快速反查原始词(比如用于日志或替换)。这不是必须的,但比存 vector 下标更鲁棒——尤其词表动态增删时。
- 不要对每个字符做
std::hash<char></char>——毫无意义,字符就 256 种,直接当数组下标用 - 避免用
std::hash<:string></:string>当匹配依据:哈希碰撞会导致漏检,敏感词过滤零容忍 - 如果词表来自文件,读入后先去重:
std::unordered_set<:string> dedup</:string>,再批量 insert 到 AC 树,否则重复词会浪费节点和搜索时间
实际部署时最容易被忽略的细节
不是算法写对就完事,真实场景下几个硬伤常导致线上失效:
- 大小写问题:用户输入 “SEX” 和词表里的 “sex” 必须统一处理。建议预处理时全部转小写(
std::tolower),但注意 locale——用std::locale::classic()避免中文 locale 下非 ASCII 字符异常 - 全角/半角:比如“ABC” vs “ABC”,单纯哈希或 AC 都无法跨编码匹配。要么前端统一转换,要么在构建 AC 树前对敏感词做标准化(例如 Unicode NFKC 归一化,需 ICU 库)
- 内存布局:节点用
std::vector<node></node>而非new Node[],避免碎片和泄漏;总节点数上限可预估(所有词长度和 + 1),预留空间防 reallocate - 线程安全:AC 树构建完是只读的,搜索函数可并发调用;但若支持热更新(运行中 reload 词表),就得加读写锁,且替换指针要原子操作
AC 自动机的节点数和搜索耗时都严格取决于敏感词总量与平均长度,跟文本长度几乎无关——这才是它被工业级系统选用的根本原因。别被“哈希”二字带偏,真正的瓶颈永远在构建和内存局部性上。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











