通过DFA算法实现大文本敏感词过滤

敏感词过滤就是把文本中的违规词汇替换成“*”或者直接过滤。实际上有很多种实现方法

1.字符串匹配

通过Java的contains进行字符串匹配。使用这种的好处就是实现起来方便,但缺点就是文本和敏感词汇量大的时候它的效率是非常低的,并且是无法告诉我们具体的敏感词和下标。它只适合用于小规模文本或词汇量小的场景下

  1. public static boolean containsSensitiveWord(String text) {
  2. ReadTxtFiles readTxtFiles = new ReadTxtFiles();
  3. //获取敏感词文本词
  4. List<String> sensitiveWords = readTxtFiles.readTxtFiles(DAFFilter.class.getClassLoader().getResource("lexfiles").getFile());
  5. //进行循环判断
  6. for (String sensitiveWord : sensitiveWords) {
  7. if (text.contains(sensitiveWord)) {
  8. return true;
  9. }
  10. }
  11. return false;
  12. }
  13. public static void main(String[] args) {
  14. String text = "张三是个大xx";
  15. if (containsSensitiveWord(text)) {
  16. System.out.println("文本中存在敏感词");
  17. } else {
  18. System.out.println("文本中不存在敏感词");
  19. }
  20. }

2.前缀树

以下博主说的比较详细,就不过多阐述

经典数据结构——前缀树

3.DFA算法

DFA(Deterministic Finite Automaton,确定有限状态自动机)是一种用于字符串匹配和文本搜索的经典算法。特别适用于敏感词过滤

首先,为每个敏感词构建DFA。这个过程从一个初始状态开始,对于敏感词中的每个字符,如果当前状态下没有对应该字符的转换,则创建一个新的状态,并添加一条从当前状态到新状态的转换,这条转换由该字符触发。如果已经有了对应该字符的转换,则沿着这条转换到达下一个状态。重复这个过程直到敏感词的每个字符都被处理。最后一个字符对应的状态被标记为接受状态,意味着到达这个状态表示找到了一个敏感词。

假设我们的敏感词汇包含"abc","abd"。首先将这两个字符串构建不同的DFA:

  • 初始状态0,对于输入c转到状态1,对于输入b转到状态4。
  • 状态1,对于输入a转到状态2。
  • 状态2,对于输入t转到状态3,状态3是接受状态,意味着找到了敏感词cat。
  • 同理,状态4对于输入a转到状态5,状态5对于输入t转到状态6,状态6是接受状态,意味着找到了敏感词bat。

过滤文本:

当需要检测一段文本时,从DFA的初始状态和文本的第一个字符开始。对于文本中的每个字符,根据当前状态和字符确定下一个状态。如果存在对应字符的转换,则跟随这条转换到达下一个状态;如果不存在,则返回到初始状态。如果在某个点达到了接受状态,意味着匹配到了一个敏感词。然后可以选择标记或替换该敏感词,并继续处理文本的剩余部分。

现在,如果我们需要过滤一段文本:“我有一只狗叫:abc”,我们从初始状态0开始,遇到c转到状态1,接着a转到状态2,然后t转到状态3,此时达到接受状态,意味着我们发现了敏感词cat。根据需要,可以对这个敏感词进行标记或替换,然后继续处理文本的剩余部分。

  1. public class DAFFilter {
  2. // 构建敏感词的DFA
  3. private Set<String> sensitiveWords;
  4. private Map<Character, Object> dfa;
  5. public DAFFilter(Set<String> sensitiveWords) {
  6. this.sensitiveWords = sensitiveWords;
  7. buildDFA();
  8. }
  9. // 构建DFA
  10. private void buildDFA() {
  11. dfa = new HashMap<>();
  12. for (String word : sensitiveWords) {
  13. Map<Character, Object> current = dfa;
  14. for (int i = 0; i < word.length(); i++) {
  15. char c = word.charAt(i);
  16. if (!current.containsKey(c)) {
  17. current.put(c, new HashMap<Character, Object>());
  18. }
  19. if (i == word.length() - 1) {
  20. ((Map<Character, Object>) current.get(c)).put('!', '!'); // 使用一个特殊的字符表示敏感词的结尾
  21. } else {
  22. current = (Map<Character, Object>) current.get(c);
  23. }
  24. }
  25. }
  26. }
  27. // 过滤敏感词
  28. public String filter(String text) {
  29. StringBuilder result = new StringBuilder();
  30. for (int i = 0; i < text.length(); i++) {
  31. char c = text.charAt(i);
  32. Map<Character, Object> current = dfa;
  33. int j = i;
  34. while (current.containsKey(c)) {
  35. if (((Map<Character, Object>) current.get(c)).containsKey('!')) {
  36. for (int k = i; k <= j; k++) {
  37. result.append("*"); // 将敏感词替换为*
  38. }
  39. i = j; // 更新i
  40. break;
  41. } else {
  42. if (j == text.length() - 1) {
  43. break;
  44. }
  45. current = (Map<Character, Object>) current.get(c);
  46. c = text.charAt(++j);
  47. }
  48. }
  49. result.append(text.charAt(i)); // 添加未被替换的字符
  50. }
  51. return result.toString();
  52. }
  53. public static void main(String[] args) {
  54. ReadTxtFiles readTxtFiles = new ReadTxtFiles();
  55. //获取敏感词词
  56. Set<String> sensitiveWords =readTxtFiles.readTxtFiles(DAFFilter.class.getClassLoader().getResource("lexfiles").getFile()).stream().collect(Collectors.toSet());
  57. DAFFilter dafFilter = new DAFFilter(sensitiveWords);
  58. System.out.println(dafFilter.filter("张三是个大傻比"));
  59. }
  60. }

运行结果:

敏感词汇:

以上只是实现简单的敏感词过滤功能,如果需要可以扩展

推荐基于DFA算法实现的高性能Java敏感词过滤工具

GitHub - houbb/sensitive-word: 👮‍♂️The sensitive word tool for java.(敏感词/违禁词/违法词/脏词。基于 DFA 算法实现的高性能 java 敏感词过滤工具框架。请勿发布涉及政治、广告、营销、翻墙、违反国家法律法规等内容。高性能敏感词检测过滤组件,附带繁体简体互换,支持全角半角互换,汉字转拼音,模糊搜索等功能。)