如果项目需要你实现敏感词过滤功能,如何实现?
- 标签:后端、场景题
🏛 业务架构师
敏感词过滤我做的第一件事不是选算法,而是对齐合规要求:词库来源(监管通报/行业词库)、处置要求(拦截、替换星号、还是转人工)、误杀的容忍度——这决定技术方案的激进程度。内容安全是合规风险,出一次监管通报可能就是产品下架,比性能问题严重一个量级。
算法方案
- DFA / Trie 前缀树:把词库构建成状态机,扫描文本沿树转移,命中即命中词,复杂度与文本长度线性相关、与词库规模无关,Java 侧常用 HashMap 嵌套实现节点,百万级词库毫秒级匹配
- AC 自动机:Trie 加失败指针,多模式串一次扫描全匹配,适合大文本高吞吐场景(评论流、IM)
java
// Trie节点结构示意
class Node { Map<Character, Node> next; boolean isEnd; }工程上的关键设计
- 词库热更新:运营后台增删词后秒级生效(双缓冲原子替换词库树),监管新增违禁词不能等发版
- 分级处置:色情暴恐直接拦截,广告导流替换,灰色词转人工审核——一刀切拦截会杀死正常内容生态
- 变体对抗:拼音、同音字、字符插入("敏感词")、繁简体归一化预处理,纯 Trie 挡不住故意绕过,需要 NLP 模型兜底
- 性能隔离:过滤服务独立部署,词库常驻内存,避免每条消息重建树
投入产出视角:中小平台"开源 DFA + 热更词库 + 人工审核队列"就够;日均亿级 UGC 且监管高压的平台才需要自研模型。先满足合规底线,再谈对抗升级,别一开始就上大模型审核烧钱。
🏗 系统架构师
敏感词过滤的性能瓶颈永远在字符串匹配上,我的方案是 AC 自动机(Aho-Corasick),并重点讲工程化和热更新。
算法选型
- 简单 DFA(Trie 树逐词匹配):十万词库要对每个词各查一遍,复杂度 O(文本长 × 词数),不可用。
- AC 自动机:在 Trie 上加失配指针,一次扫描文本就能同时匹配所有词,复杂度 O(文本长 + 匹配数),与词库大小无关。十万级词库构建后内存几十 MB,单机几十万 QPS 没压力。Java 生态可直接用 AhoCorasickDoubleArrayTrie(双数组实现,省内存且查询快)。
工程化设计(架构师差异点)
- 词库热更新:词库来自运营后台,随时增删。我用双 buffer 原子切换:新词库在后台构建好新自动机,构建完成后一个
volatile引用原子替换旧引用,读请求永远拿完整结构,替换瞬间无锁无停顿——绝不能在原结构上边改边查。 - 分层过滤:第一层本地 AC 自动机挡住 99% 正常内容;命中疑似命中的再走异步精查(正则、变体还原)+ 人工审核队列,同步链路只做轻计算。
- 变体对抗:全角/半角、拼音、间隔符("敏__感__词")先做归一化预处理再匹配,预处理管道用责任链组织。
多语言与规模 中文按字符匹配即可;英文要处理词边界避免 "scrap" 命中 "rape",可按词分词后匹配。亿级内容审核不再用内存自动机,改为 Flink 流式作业 + 词库广播缓存,架构上把计算随数据规模换挡。
💻 全栈开发专家
敏感词过滤我落地过两版,核心是 DFA(Trie 树 + 状态转移),量大再上 AC 自动机。
实现(DFA / 前缀树)
把词库构建成嵌套 Map,isEnd 标记成词:
java
// 构建:敏感词 "傻瓜" -> {傻: {瓜: {isEnd: true}}}
Map<Character, Object> node = dict;
for (char c : word.toCharArray()) {
node = (Map<Character, Object>) node.computeIfAbsent(c, k -> new HashMap<>());
}
node.put(END, true);
// 匹配:从每个位置沿树走,走到 isEnd 命中匹配复杂度近似 O(n),比逐个 String.contains 遍历词库快几个数量级。
生产实践要点
- 词库放配置中心/管理后台,热更新:新词库构建完成后原子替换引用(
volatile交换),不要边改边查。 - 归一化预处理:全角转半角、繁简转换、去零宽字符,否则"傻 瓜"、"sex"全绕过。
- 命中后策略可配:拒绝、替换
*、送人工审核。 - 词库十万级以上、且要一次扫描多模式匹配,换 AC 自动机(Hutool 的
WordTree或自己实现失败指针),避免 DFA 每个起点都重走。
踩过的坑 用户会用拼音首字母、同音字、插入符号(傻.瓜)绕过,纯算法防不住,必须加"变形词清洗 + 命中密度"这类运营策略层,并留命中日志供风控回溯。