找回密码
 立即注册
首页 业界区 安全 KMP、Trie树 、AC自动机‌ ,三大算法实现 netty 敏感词 ...

KMP、Trie树 、AC自动机‌ ,三大算法实现 netty 敏感词 优雅 过滤

数察啜 2025-5-30 13:45:53
尼恩说在前面:

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、shein 希音、shopee、百度、网易的面试资格,遇到很多很重要的面试题:
<blockquote><ul>IM 敏感词过滤,  方案有哪些?
10万QPS下如何保证过滤延迟 Trie树‌演进本质‌:</strong>Trie树通过空间换时间和结构化存储,解决了BF算法在处理多模式串、前缀匹配时的低效问题,是算法设计从“暴力遍历”到“智能索引”的典型演进。</p>Trie 树的实现敏感词过滤

Trie 树主要有两个操作
(1)将字符串集合构造成 Trie 树。这个过程分解开来的话,就是一个将字符串插入到 Trie 树的过程。
(2)然后是在 Trie 树中查询一个字符串。
trie树实现敏感词过滤参考代码:
  1. @Test
  2.     public void test1(){
  3.         Set<String>  sensitiveWords=new HashSet<>();
  4.         sensitiveWords.add("shit");
  5.         sensitiveWords.add("傻蛋");
  6.         sensitiveWords.add("笨蛋");
  7.         String text="你是傻蛋啊";
  8.         for(String sensitiveWord:sensitiveWords){
  9.             if(text.contains(sensitiveWord)){
  10.                 System.out.println("输入的文本存在敏感词。——" + sensitiveWord);
  11.                 break;
  12.             }
  13.         }
  14.     }
复制代码
在 Trie 树中,查找某个字符串的时间复杂度是多少?
如果要在一组字符串中,频繁地查询某些字符串,用 Trie 树会非常高效。
构建 Trie 树的过程,需要扫描所有的字符串,时间复杂度是 O(n)(n 表示所有字符串的长度和)。
但是一旦构建成功之后,后续的查询操作会非常高效。
构建好 Trie 树后,在其中查找字符串的时间复杂度是 O(k),k 表示要查找的字符串的长度。
实现了Trie树的开源框架

可以看出, Trie 树的核心原理其实很简单,就是通过公共前缀来提高字符串匹配效率。
Apache Commons Collections这个库中就有 Trie 树实现:
1.png
  1. 主串:ABABABCABABABD  
  2.         ↓   模式串从第3个字符继续匹配(`ABABD`的第3个字符`A`对齐主串`C`的位置)  
  3. 模式串:  ABABD  
复制代码
双数组 Trie 树(Double-Array Trie,DAT)

Trie 树是一种利用空间换时间的数据结构,占用的内存会比较大。
也正是因为这个原因,实际工程项目中都是使用的改进版 Trie 树例如双数组 Trie 树(Double-Array Trie,DAT)。
DAT 的设计者是日本的 Aoe Jun-ichi,Mori Akira 和 Sato Takuya,他们在 1989 年发表了一篇论文《An Efficient Implementation of Trie Structures》,详细介绍了 DAT 的构造和应用,
原作者写的示例代码地址:https://github.com/komiya-atsushi/darts-java/blob/e2986a55e648296cc0a6244ae4a2e457cd89fb82/src/main/java/darts/DoubleArrayTrie.java。
相比较于 Trie 树,DAT 的内存占用极低,可以达到 Trie 树内存的 1%左右。
DAT 在中文分词、自然语言处理、信息检索等领域有广泛的应用,是一种非常优秀的数据结构。
Trie树的局限性‌

传统Trie树在处理敏感词匹配时存在以下核心缺陷:
(1)‌回溯成本高‌:当字符匹配失败时需退回到根节点重新匹配,导致对同一文本位置多次扫描,时间复杂度达 O(n×m)(n为文本长度,m为敏感词最大长度)
(2)‌多模式匹配低效‌:逐个敏感词独立判断,无法利用词汇间的关联性
(3)‌长尾性能劣化‌:敏感词库规模增大时,匹配耗时线性增长,难以应对工业级海量词库场景
AC自动机

什么是AC自动机?

简单理解AC自动机 就是 Tire树  + KMP,
关于KMP算法,可以参考网上文章,其实就是trie树 + 失配指针(下图动画中的虚线)
下面是对AC自动机构建过程的详细介绍:

  • 构建Trie树:首先构建一个Trie树,用于存储所有字符串。每个节点代表一个字符,从根节点到任意节点的路径代表一个字符串的前缀。
  • 创建失配指针(Failure Pointers):这些指针指向Trie树中的另一节点,当在某一节点上的字符匹配失败时,算法会通过失效指针跳转到另一节点继续匹配,而不是从头开始。
  • 搜索:在给定文本中进行搜索时,AC自动机沿着Trie树移动,同时在匹配失败时 ,  使用Failure Pointer 失效指针进行快速跳转。
AC自动机算法演示动画,下面动画清晰的演示了AC自动机算法原理
2.jpeg

为什么用AC自动机

AC自动机(Aho-Corasick算法)是一种用于字符串搜索的算法,它能够高效地在一段文本中查找多个模式串/字符串。
这个算法由Alfred V. Aho和Margaret J. Corasick于1975年共同提出。
AC自动机优化了字典树匹配的过程:在字典树的暴力匹配过程中,每当匹配失败,就会从下一个位置重新开始匹配,这导致了重复的匹配操作。
为了提高效率,AC自动机算法借鉴KMP算法的思想,通过在每个节点添加一个失配链接点,使得在匹配失败后能直接跳转到相应的下一个节点进行判断,从而避免重复的判断过程。
AC自动机通过‌预处理Fail指针‌和‌多模式状态机跳转‌,在Trie树基础上实现性能质的飞跃,参考后面的动画GIF
3.png


  • 在 Trie匹配过程中,一些模式串之间存在一部分重叠,也就意味着在匹配 sherhs 过程,
  • 如果能匹配到点1,后续一定可以匹配到点2
  • 如果在点1向下匹配失败时候,可以直接跳到点2,继续向下匹配
  • 通过增加两点之间联系,减少回溯过程
  • 关联的条件是1的后缀与2的前缀相同(类似 KMP 思想)
AC自动机的优势

AC自动机通过两项核心改进突破Trie树瓶颈:
(1)‌Fail指针机制‌

  • ‌KMP思想的移植‌:  为每个节点预计算最长可复用后缀对应的状态(Fail指针),匹配失败时直接跳转而非回溯,消除重复扫描。
  • ‌跳转逻辑示例‌:若敏感词集包含 she 和 he,当文本出现 she 时,匹配到 e 节点触发 he 的终止状态,无需重新从 h 开始。
(2)多模式并行匹配‌

  • 单次文本扫描即可检测所有敏感词,时间复杂度降至 O(n)(与词库规模无关,n为文本长度)
  • 通过构建Trie树时预置Fail指针(BFS遍历实现),确保匹配阶段无回溯
性能对比

‌维度‌‌Trie树‌‌AC自动机‌‌时间复杂度‌O(n*m)(n为文本长度,m为敏感词最大长度)O(n)(n为文本长度)‌空间利用率‌共享前缀节省空间增加Fail指针存储,但整体仍优于哈希表‌适用场景‌小规模词库、低并发场景万级词库、高并发实时过滤(如社交平台)‌扩展性‌无法处理模糊匹配结合Wildcard优化可支持通配符开源的AC自动机实现

基于双数组 Trie 结构的 Aho Corasick 算法的极速实现。
其速度是简单实现的 5 到 9 倍,或许是目前最快的实现
AhoCorasickDoubleArrayTrie:https://github.com/hankcs/AhoCorasickDoubleArrayTrie
用法:
  1. 主串:ABCDABE...
  2. 模式:ABCDABD(前6字符匹配,第7字符不匹配,E≠D)
复制代码
  1. 主串:ABCDABE...
  2. 模式:    ABCDABD(直接从模式串的第3 个字符 `C` 开始对比)
复制代码
测试结果:
AhoCorasickDoubleArrayTrie 与 robert-bor 的 aho-corasick 进行了比较,ACDAT 代表 AhoCorasickDoubleArrayTrie,Naive 代表 aho-corasick,结果是:
  1. /**
  2. * 敏感词过滤器核心类,基于Trie树实现高效匹配
  3. * 特点:支持中文敏感词检测,时间复杂度O(n)
  4. */
  5. public class SensitiveWordFilter {
  6.   
  7.     // Trie树根节点(空节点)
  8.     private final TrieNode root = new TrieNode();
  9.     // 敏感词替换字符串
  10.     private static final String REPLACEMENT = "***";
  11.     /**
  12.      * Trie树节点静态内部类
  13.      * 采用HashMap存储子节点实现动态扩展
  14.      */
  15.     private static class TrieNode {
  16.         // 子节点映射表 <字符, 对应子节点>
  17.         Map<Character, TrieNode> children = new HashMap<>();
  18.         // 标记当前节点是否为某个敏感词的结尾
  19.         boolean isEnd;
  20.     }
  21.     /**
  22.      * 加载敏感词库构建Trie树
  23.      * @param keywords 敏感词集合
  24.      */
  25.     public void loadKeywords(Set<String> keywords) {
  26.         for (String word : keywords) {
  27.             TrieNode node = root; // 从根节点开始构建
  28.             for (int i = 0; i < word.length(); i++) {
  29.                 char c = word.charAt(i);
  30.                 // 如果当前字符不存在于子节点,则新建分支
  31.                 if (!node.children.containsKey(c)) {
  32.                     node.children.put(c, new TrieNode());
  33.                 }
  34.                 // 移动到下一级节点
  35.                 node = node.children.get(c);
  36.             }
  37.             // 标记敏感词结束位置
  38.             node.isEnd = true;
  39.         }
  40.     }
  41.     /**
  42.      * 执行敏感词过滤主方法
  43.      * @param text 待过滤文本
  44.      * @return 过滤后的安全文本
  45.      */
  46.     public String filter(String text) {
  47.         StringBuilder result = new StringBuilder();
  48.         int start = 0; // 文本扫描起始位置
  49.         
  50.         while (start < text.length()) {
  51.             // 检测从start位置开始的敏感词
  52.             int matchLength = checkSensitiveWord(text, start);
  53.             if (matchLength > 0) {
  54.                 // 存在敏感词则替换
  55.                 result.append(REPLACEMENT);
  56.                 start += matchLength; // 跳过已检测部分
  57.             } else {
  58.                 // 无敏感词则保留原字符
  59.                 result.append(text.charAt(start++));
  60.             }
  61.         }
  62.         return result.toString();
  63.     }
  64.     /**
  65.      * 检查指定位置开始的敏感词
  66.      * @param text 待检测文本
  67.      * @param startIndex 检测起始位置
  68.      * @return 匹配到的敏感词长度(未匹配返回0)
  69.      */
  70.     private int checkSensitiveWord(String text, int startIndex) {
  71.         TrieNode tempNode = root;
  72.         int matchLength = 0;
  73.         
  74.         for (int i = startIndex; i < text.length(); i++) {
  75.             char c = text.charAt(i);
  76.             // 当前字符不匹配时终止检测
  77.             if (!tempNode.children.containsKey(c)) {
  78.                 break;
  79.             }
  80.             // 移动到下一级节点
  81.             tempNode = tempNode.children.get(c);
  82.             matchLength++;
  83.             // 遇到结束标记说明匹配成功
  84.             if (tempNode.isEnd) {
  85.                 return matchLength;
  86.             }
  87.         }
  88.         return 0; // 未匹配到完整敏感词
  89.     }
  90.     // 测试用例
  91.     public static void main(String[] args) {
  92.         SensitiveWordFilter filter = new SensitiveWordFilter();
  93.         // 模拟敏感词库
  94.         Set<String> sensitiveWords = new HashSet<>();
  95.         sensitiveWords.add("暴力");
  96.         sensitiveWords.add("敏感词");
  97.         sensitiveWords.add("测试");
  98.         
  99.         // 构建Trie树
  100.         filter.loadKeywords(sensitiveWords);
  101.         
  102.         // 测试过滤效果
  103.         String testText = "这是一段包含暴力内容和敏感词的测试文本";
  104.         System.out.println(filter.filter(testText));
  105.         // 输出:这是一段包含‌***内容和***‌的***文本
  106.     }
  107. }
复制代码
在英文测试中,AhoCorasickDoubleArrayTrie 的速度提高了 5 倍。
在中文测试中,AhoCorasickDoubleArrayTrie 的速度提高了 9 倍。
此测试在 i7 2.0GHz 处理器、-Xms512m -Xmx512m -Xmn256m 的环境下进行。
Netty 敏感词过滤的技术选型

在 Netty 框架中实现敏感词过滤时,需综合考虑 ‌性能、内存占用、开发复杂度‌ 等因素。
以下是各算法特性对比与选型建议:
1. ‌算法特性对比‌

算法/结构适用场景性能表现内存占用多模式匹配能力实现复杂度‌BF 算法‌小规模敏感词库、低频匹配O(mn),极端场景下性能急剧下降低不支持极简单‌Trie 树‌中等规模词库、前缀匹配需求匹配时间 O(L)(L为字符串长)高(空间换时间)14支持中等‌双数组 Trie (DAT)‌海量敏感词库、内存敏感场景单模式匹配极快,但多模式需多次回溯‌极低弱支持较高(需处理状态转移)‌AC 自动机‌大规模词库、实时多模式匹配‌一次扫描完成全部匹配‌ O(n)(n为主串长)中等(需维护失败指针)强支持较高2. ‌选型决策‌

根据 Netty 高并发、低延迟的特性,推荐优先级如下:
首选方案:AC 自动机


  • ‌优势‌

    • 多模式匹配效率碾压其他方案,单次文本扫描即可检测所有敏感词。

  • 支持动态词库更新(通过重建或增量维护 Trie 树)。

    • 可结合内存优化(如压缩 Trie 结构)平衡性能与资源消耗。

  • ‌适用场景‌

    • 需实时过滤大量敏感词(如聊天系统、内容审核)。

  • 敏感词数量超过 1 万且需要高频匹配的场景。
次选方案:双数组 Trie (DAT)


  • ‌优势‌

    • 内存占用极低,适合嵌入式或资源受限环境。

  • 单模式匹配速度快(如仅需检测少量固定关键词)。
  • ‌局限‌

    • 多模式匹配需多次扫描文本,性能低于 AC 自动机。

不推荐方案


  • Trie 树‌:内存占用高,且多模式匹配效率低于 AC 自动机。
  • BF 算法‌:仅适用于测试验证,实际生产环境性能不达标。
3. ‌开源实现框架参考‌


  • Java AC 自动机库‌:org.ahocorasick(轻量级,支持 Trie 树构建与多模式匹配) 。
  • 双数组 Trie 实现‌:com.github.komoot.datrie(高效 DAT 实现,适用于静态词库) 。
在 Netty 中实现敏感词过滤,‌AC 自动机是综合最优解‌,尤其在处理海量敏感词和高并发请求时表现卓越。若对内存有极端限制,可考虑双数组 Trie,但需接受多模式匹配性能损耗。
基于 AC 自动机算法的 Netty 敏感词风控处理器实现

下面是基于 AC 自动机算法的 Netty 敏感词风控处理器实现示例。
当检测到敏感词时,回复“您发送的消息,带有敏感内容”:
  1. Trie<String, String> trie = new PatriciaTrie<>();
  2. trie.put("Abigail", "student");
  3. trie.put("Abi", "doctor");
  4. trie.put("Annabel", "teacher");
  5. trie.put("Christina", "student");
  6. trie.put("Chris", "doctor");
  7. Assertions.assertTrue(trie.containsKey("Abigail"));
  8. assertEqual
  9. s("{Abi=doctor, Abigail=student}", trie.prefixMap("Abi").toString());
  10. assertEquals("{Chris=doctor, Christina=student}", trie.prefixMap("Chr").toString());
复制代码
同时,需要实现 AC 自动机算法的类:
  1. <dependency>
  2.   <groupId>com.hankcs</groupId>
  3.   aho-corasick-double-array-trie</artifactId>
  4.   <version>1.2.3</version>
  5. </dependency>
复制代码
在上面的示例代码中:

  • SensitiveWordHandler 类继承自 ChannelInboundHandlerAdapter,用于处理 Netty 通道中的入站消息。
  • 在 SensitiveWordHandler 的构造函数中,传入敏感词列表,并构建 AC 自动机。
  • channelRead 方法在通道读取消息时被调用。它从消息对象中获取文本内容,然后使用 AC 自动机进行敏感词匹配。如果匹配到敏感词,则通过 ctx.writeAndFlush 方法向客户端回复提示信息“您发送的消息,带有敏感内容”,并终止后续的消息处理流程;如果未匹配到敏感词,则继续将消息传递给下一个处理器。
AcAutomaton 类实现了 AC 自动机算法:

  • 使用 TrieNode 类表示 AC 自动机的节点,每个节点包含其子节点映射、对应的单词(当节点是某个敏感词的结尾时)以及失败指针。
  • build 方法用于构建 AC 自动机。首先构建 Trie 树,将所有敏感词插入到树中;然后构建失败指针,使用广度优先搜索的方式为每个节点设置失败指针,以便在匹配过程中能够快速跳转。
  • match 方法用于在文本中匹配敏感词。它从文本的每个字符开始,沿着 AC 自动机的节点进行匹配。如果在某个节点匹配到敏感词(即节点的 word 属性不为 null),则返回 true 表示存在敏感词。
需要注意的是,这只是一个简单的示例,实际应用中可能需要根据具体的需求对代码进行扩展和完善,例如处理编码问题、支持不同格式的消息、优化性能等。此外,在构建敏感词列表时,应确保敏感词的准确性和完整性,以提高敏感词过滤的效果。
AC 自动机算法‌ 优化


  • 异步检测‌:将敏感词匹配任务提交至独立线程池,避免阻塞 I/O 线程
  • 动态加载‌:通过 WatchService 监控词库文件变更实现热更新
  • 分级响应‌:根据敏感词级别返回不同提示(如警告/直接封禁)
  • 日志记录‌:记录触发敏感词的原始消息和用户信息用于审计
  • 模糊匹配‌:集成正则表达式处理变体敏感词(如拼音、谐音)
1. 异步检测:将敏感词匹配任务提交至独立线程池
  1. // Collect test data set
  2.         TreeMap<String, String> map = new TreeMap<String, String>();
  3.         String[] keyArray = new String[]
  4.                 {
  5.                         "hers",
  6.                         "his",
  7.                         "she",
  8.                         "he"
  9.                 };
  10.         for (String key : keyArray)
  11.         {
  12.             map.put(key, key);
  13.         }
  14.         // Build an AhoCorasickDoubleArrayTrie
  15.         AhoCorasickDoubleArrayTrie<String> acdat = new AhoCorasickDoubleArrayTrie<String>();
  16.         acdat.build(map);
  17.         // Test it
  18.         final String text = "uhers";
  19.         List> wordList = acdat.parseText(text);
复制代码
2. 动态加载:通过 WatchService 监控词库文件变更实现热更新
  1. Parsing English document which contains 3409283 characters, with a dictionary of 127142 words.
  2.                        Naive                  ACDAT
  3. time                   607                    102
  4. char/s                 5616611.20             33424343.14
  5. rate                   1.00                   5.95
  6. ===========================================================================
  7. Parsing Chinese document which contains 1290573 characters, with a dictionary of 146047 words.
  8.                        Naive                  ACDAT
  9. time                   319                    35
  10. char/s                 2609156.74             23780600.00
  11. rate                   1.00                   9.11
  12. ===========================================================================
复制代码
3. 分级响应:根据敏感词级别返回不同提示
  1. import io.netty.channel.ChannelHandlerContext;
  2. import io.netty.channel.ChannelInboundHandlerAdapter;
  3. import io.netty.util.AttributeMap;
  4. import java.util.List;
  5. /**
  6. * 敏感词过滤处理器,用于检测入站消息是否包含敏感词
  7. */
  8. public class SensitiveWordHandler extends ChannelInboundHandlerAdapter {
  9.     private AcAutomaton acAutomaton;
  10.     /**
  11.      * 构造函数,传入敏感词列表并构建AC自动机
  12.      *
  13.      * @param sensitiveWords 敏感词列表
  14.      */
  15.     public SensitiveWordHandler(List<String> sensitiveWords) {
  16.         acAutomaton = new AcAutomaton();
  17.         acAutomaton.build(sensitiveWords); // 构建AC自动机
  18.     }
  19.     /**
  20.      * 处理入站消息
  21.      *
  22.      * @param ctx 通道处理上下文
  23.      * @param msg 入站消息
  24.      * @throws Exception 处理过程中可能出现的异常
  25.      */
  26.     @Override
  27.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  28.         AttributeMap msgAttr = (AttributeMap) msg; // 将消息转换为AttributeMap类型
  29.         String content = msgAttr.get("content"); // 获取消息中的文本内容
  30.         if (acAutomaton.match(content)) { // 调用AC自动机的match方法检测是否包含敏感词
  31.             // 如果检测到敏感词,回复提示信息
  32.             ctx.writeAndFlush("您发送的消息,带有敏感内容");
  33.             return; // 结束当前方法执行,不再向下传递消息
  34.         }
  35.         ctx.fireChannelRead(msg); // 如果未检测到敏感词,继续将消息传递给下一个处理器
  36.     }
  37. }
复制代码
4. 日志记录:记录触发敏感词的原始消息和用户信息
  1. import java.util.List;
  2. import java.util.Map;
  3. import java.util.HashMap;
  4. import java.util.Queue;
  5. import java.util.LinkedList;
  6. /**
  7. * 基于AC自动机算法实现敏感词匹配
  8. */
  9. public class AcAutomaton {
  10.     /**
  11.      * Trie树节点类
  12.      */
  13.     private static class TrieNode {
  14.         Map<Character, TrieNode> children; // 子节点映射,键为字符,值为对应的子节点
  15.         String word; // 如果该节点是一个单词的结尾,则存储该单词
  16.         TrieNode fail; // 失败指针,用于快速跳转到可能的匹配位置
  17.         /**
  18.          * 构造函数,初始化节点
  19.          */
  20.         public TrieNode() {
  21.             children = new HashMap<>();
  22.             word = null;
  23.             fail = null;
  24.         }
  25.     }
  26.     private TrieNode root; // AC自动机的根节点
  27.     /**
  28.      * 构造函数,初始化根节点
  29.      */
  30.     public AcAutomaton() {
  31.         root = new TrieNode();
  32.     }
  33.     /**
  34.      * 构建AC自动机
  35.      *
  36.      * @param sensitiveWords 敏感词列表
  37.      */
  38.     public void build(List<String> sensitiveWords) {
  39.         // 构建Trie树
  40.         for (String word : sensitiveWords) {
  41.             TrieNode node = root;
  42.             for (char c : word.toCharArray()) { // 遍历敏感词的每个字符
  43.                 if (!node.children.containsKey(c)) { // 如果当前节点没有该字符对应的子节点
  44.                     node.children.put(c, new TrieNode()); // 创建新的子节点
  45.                 }
  46.                 node = node.children.get(c); // 移动到子节点
  47.             }
  48.             node.word = word; // 标记该节点为单词结尾
  49.         }
  50.         // 构建失败指针
  51.         Queue<TrieNode> queue = new LinkedList<>(); // 用于广度优先遍历的队列
  52.       
  53.         // 初始化队列,将根节点的子节点加入队列
  54.         
  55.         for (TrieNode child : root.children.values()) {
  56.             child.fail = root; // 根节点的子节点的失败指针指向根节点
  57.             queue.add(child);
  58.         }
  59.         while (!queue.isEmpty()) { // 广度优先遍历构建失败指针
  60.             TrieNode node = queue.poll(); // 取出队列中的节点
  61.             for (Map.Entry<Character, TrieNode> entry : node.children.entrySet()) { // 遍历该节点的所有子节点
  62.                 TrieNode child = entry.getValue(); // 获取子节点
  63.                 TrieNode failNode = node.fail; // 获取当前节点的失败指针节点
  64.                 // 寻找失败指针节点的对应字符子节点
  65.                 while (failNode != null && !failNode.children.containsKey(entry.getKey())) {
  66.                     failNode = failNode.fail; // 如果失败指针节点没有对应子节点,则继续向上查找
  67.                 }
  68.                 // 设置子节点的失败指针
  69.                 child.fail = (failNode != null) ? failNode.children.get(entry.getKey()) : root;
  70.                 queue.add(child); // 将子节点加入队列
  71.             }
  72.         }
  73.     }
  74.     /**
  75.      * 在文本中匹配敏感词
  76.      *
  77.      * @param text 要匹配的文本
  78.      * @return 如果文本中包含敏感词,则返回true;否则返回false
  79.      */
  80.     public boolean match(String text) {
  81.         TrieNode p = root; // 从根节点开始匹配
  82.         for (char c : text.toCharArray()) { // 遍历文本的每个字符
  83.             // 如果当前节点不是根节点,  且没有对应字符的子节点,则沿着失败指针回溯
  84.             while (p != root && !p.children.containsKey(c)) {
  85.                 p = p.fail;
  86.             }
  87.             if (p.children.containsKey(c)) { // 如果当前节点有对应字符的子节点
  88.                 p = p.children.get(c); // 移动到子节点
  89.             }
  90.             if (p.word != null) { // 如果当前节点是一个单词结尾,则说明匹配到敏感词
  91.                 return true;
  92.             }
  93.         }
  94.         return false; // 遍历完整个文本未匹配到敏感词
  95.     }
  96. }
复制代码
5. 模糊匹配:集成正则表达式处理变体敏感词
  1. import io.netty.channel.ChannelHandlerContext;
  2. import io.netty.channel.ChannelInboundHandlerAdapter;
  3. import io.netty.util.AttributeMap;
  4. import java.util.List;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. public class SensitiveWordHandler extends ChannelInboundHandlerAdapter {
  8.     private AcAutomaton acAutomaton;
  9.     private ExecutorService executorService; // 独立线程池
  10.     public SensitiveWordHandler(List<String> sensitiveWords, int threadPoolSize) {
  11.         acAutomaton = new AcAutomaton();
  12.         acAutomaton.build(sensitiveWords);
  13.         executorService = Executors.newFixedThreadPool(threadPoolSize); // 创建固定大小的线程池
  14.     }
  15.     @Override
  16.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  17.         AttributeMap msgAttr = (AttributeMap) msg;
  18.         String content = msgAttr.get("content");
  19.         // 将敏感词检测任务提交至线程池异步处理
  20.         executorService.submit(() -> {
  21.             try {
  22.                 if (acAutomaton.match(content)) {
  23.                     ctx.writeAndFlush("您发送的消息,带有敏感内容");
  24.                 }
  25.             } catch (Exception e) {
  26.                 e.printStackTrace();
  27.             }
  28.         });
  29.         ctx.fireChannelRead(msg);
  30.     }
  31.     @Override
  32.     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
  33.         executorService.shutdown(); // 当连接关闭时,优雅地关闭线程池
  34.     }
  35. }
复制代码
通过以上优化,敏感词检测程序具备了异步检测、动态加载、分级响应、日志记录和模糊匹配等功能。
这些改进提高了程序的性能、灵活性和实用性,使其能够更好地适应实际应用场景中的需求。
遇到问题,找老架构师取经

借助此文的问题 套路 ,大家可以 放手一试,保证 offer直接到手,还有可能会 涨薪  100%-200%。
后面,尼恩java面试宝典回录成视频, 给大家打造一套进大厂的塔尖视频。
在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,里边有大量的大厂真题、面试难题、架构难题。
很多小伙伴刷完后, 吊打面试官, 大厂横着走。
在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。
另外,如果没有面试机会,可以找尼恩来改简历、做帮扶。
遇到职业难题,找老架构取经, 可以省去太多的折腾,省去太多的弯路。
尼恩指导了大量的小伙伴上岸,前段时间,刚指导 32岁 高中生,冲大厂成功。特批 成为 架构师,年薪 50W,逆天改命 !!!。
狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由” 。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册