高频、长词库、低延迟场景必须用DFA;需校验UTF8编码防乱码,限制单词长度≤32防栈溢出,暴露监控接口验证效果。

敏感词过滤该用 DFA 还是正则
直接说结论:高频、长词库、低延迟场景必须用 DFA;临时校验、词少(Regex 更快上手但容易翻车。
正则在匹配“*嫖*赌*毒*”这类模糊模式时看似方便,但遇到“嫖娼”“嫖客”“嫖资”这种前缀复用词,Regex 会反复回溯,词库超 200 条就明显卡顿;而 DFA 是预编译状态机,匹配耗时只和文本长度有关,和词库大小几乎无关。
常见错误现象:Regex.Replace(input, @"嫖|赌|毒|黄", "***") 看似能用,但漏掉“嫖娼”(因“嫖”被替换了,“娼”失去上下文),也挡不住“氵票昌”这类变形——这不是正则的问题,是规则设计没覆盖语义边界。
DFA 构建时最常踩的三个坑
构建 DFA 不是把词塞进字典树就完事,状态转移和结束标记不处理好,要么漏匹配,要么误杀。
- 没做「最长匹配」处理:比如词库含“色情”和“色情网站”,输入“色情网站违规”,应匹配整段而非只截“色情”。需在每个节点存
isEnd同时记录wordLength,运行时持续更新最大匹配长度 - 忽略大小写/全半角兼容:用户输“sex”,你的
DFA节点还是 ASCII 的 's',直接跳过。建议预处理阶段统一转小写+全角转半角,或在构建时对等扩展节点(但内存翻倍) - 没处理「中间态终止」:像“和谐”是敏感词,但“和谐社会”不是。不能一见“和谐”就报警,得看它是否构成完整词边界。解决方案是在
DFA节点加isWholeWordOnly标记,并在匹配后检查前后字符是否为非字母数字(用char.IsLetterOrDigit()判)
C# 实现 DFA 的关键代码片段
不用第三方库,手写轻量 DFA 核心就两个类:TrieNode 和 DfaFilter。重点不在结构,而在状态推进逻辑。
示例中省略构造函数,聚焦运行时匹配:
public int MatchLength(string text, int start)
{
var node = _root;
int maxLength = 0;
for (int i = start; i maxLength)
maxLength = node.WordLength;
// 注意:这里不 return,继续走,支持最长匹配
}
return maxLength;
}
调用时循环推进:while (i 0) { /* 替换或标记 */ i += len; } else i++; }。别用 for 套 for 双重遍历,性能直接掉一个数量级。
生产环境必须加的兜底和监控
DFA 很快,但词库加载失败、节点爆炸、空指针这些错误不会报在匹配时,而是在初始化阶段静默失败。
实际部署中这几个点容易被忽略:
- 词库文件读取后必须校验
Encoding.UTF8,Windows 记事本保存的 txt 默认是 GB2312,File.ReadAllLines(path)不指定编码会乱码,导致构建的DFA节点全是“?”,线上完全失效 - 单个敏感词超长(如 50 字的营销黑话)会导致深度过大,递归构建可能栈溢出。加硬限制:
if (word.Length > 32) continue; - 没留监控入口:至少暴露一个
GetMatchCount(string text)方法,配合日志采样(比如每万次请求打一次匹配数统计),否则词库更新后效果无法验证
复杂点不在算法本身,而在词边界的定义、编码的隐式转换、以及上线后没人看的日志埋点。











