[关闭]
@eternity 2014-09-01T15:21:50.000000Z 字数 3562 阅读 2806

关于java中敏感词检测的一些总结

编程 java


之前项目里客户提出一个需求,需要对系统中使用文本转化成语音发送的功能进行敏感词检测,禁止用户提交有敏感词的语音。通过查询各方面资料,整理了大概几种方案:

  1. 项目启动时对载入敏感词库作为缓存(一个大map,敏感词为key,取任意值为value)。 对请求传入的文本分词,遍历分词结果,每个分词在map中查找,如果有值,则请求文本存在敏感词。
  2. 把敏感词库拼接成一个大的正则表达式,然后直接对文本匹配。
  3. 使用DFA(确定性有限状态自动机) DFA算法

对于方案选择,在网上参考了很多别人的代码。最简单的是方法2使用正则表达式,但是据说文本一长会有很大的效率问题。关于方法3DFA算法,由于在学校的时候算法课和编译原理没有认真听讲(惭愧= =||),直接就忽略这方法了,所以最后还是决定使用方法1。
其实方法1还是有很多可以改进的方法,后来又参考了这个帖子12楼中的方法,使用索引数组加关联数组的方式,提高了检索效率,甚至连分词的步骤都省掉了。整个实现代码如下。

  1. package com.ffcs.cbox.common.utils;
  2. import org.apache.commons.io.FileUtils;
  3. import org.apache.commons.lang.StringUtils;
  4. import java.io.IOException;
  5. import java.util.ArrayList;
  6. import java.util.HashMap;
  7. import java.util.List;
  8. import java.util.Map;
  9. /**
  10. * User: eternity
  11. * Date: 2014/8/11
  12. * Time: 16:17
  13. * 敏感词检测类
  14. * 敏感词检测初始化规则:
  15. * 将敏感词从词库载入,按照2字、3字、4字、5字等字数各生成一个敏感词哈希表。
  16. * 在将这些哈希表组成一个数组banWordsList,数组下标表示该敏感词表字数
  17. * banWordsList[2] = {某马:true,屏蔽:true,啦啦:true};
  18. * banWordsList[3] = {某个马:true,三个字:true,啦啦啦:true,小广告:true};
  19. * banWordsList[4] = {某个坏银:true,四个字符:true,哈哈哈哈:true,就爱凤姐:true};
  20. * banWordsList[5] = {某个大法好:true,五个敏感字:true};
  21. * 根据上面几组组敏感词,自动生成以下索引
  22. * 生成规则为,索引名是敏感词第一个字,值是一个int
  23. * 该int的规则为,该int转换成二进制时,第i位为1表示上面4表存在长度为i的敏感词,否则不存在长度为i的敏感词(10000)
  24. * wordIndex = {二:0x04,三:0x08,四:0x10,五:0x20,某:0x3c,啦:0x0c,哈:0x10,小:0x08,就:0x10};
  25. *
  26. * 检查规则如下:
  27. * 1,逐字检验,是否该字在wordIndex索引表中。
  28. * 2,如果不在表中,继续检验
  29. * 3,如果在表中,根据索引表该键的值,取此字以及此字后的若干字检验详细表banWordsList[索引词长]。
  30. *
  31. * 检验例子
  32. * 有一段如下文字,检验其是否包含敏感词:
  33. “我就打小广告,气死版主”
  34. ——检测“我”
  35. |-不在索引表
  36. ——检测“就”
  37. |-在索引表
  38. |-“就”的索引值是0x10,表示有4字以“就”开头的敏感词
  39. |-取“就”和后面的字共4个,组成“就打小广”
  40. |-查4字敏感词表,没有这项,继续
  41. ——检测“打”
  42. |-不在索引表
  43. ——检测“小”
  44. |-在索引表
  45. |-索引值是0x08,表示有3字长度的敏感词
  46. |-取“小”和“小”后面的字,共3个字组成一个词“小广告”
  47. |-“小广告”在3字敏感词中,此帖包含敏感词,禁止发布
  48. */
  49. public class BanWordsUtil {
  50. // public Logger logger = Logger.getLogger(this.getClass());
  51. public static final int WORDS_MAX_LENGTH = 10;
  52. public static final String BAN_WORDS_LIB_FILE_NAME = "banWords.txt";
  53. //敏感词列表
  54. public static Map[] banWordsList = null;
  55. //敏感词索引
  56. public static Map<String, Integer> wordIndex = new HashMap<String, Integer>();
  57. /*
  58. * 初始化敏感词库
  59. */
  60. public static void initBanWordsList() throws IOException {
  61. if (banWordsList == null) {
  62. banWordsList = new Map[WORDS_MAX_LENGTH];
  63. for (int i = 0; i < banWordsList.length; i++) {
  64. banWordsList[i] = new HashMap<String, String>();
  65. }
  66. }
  67. //敏感词词库所在目录,这里为txt文本,一个敏感词一行
  68. String path = BanWordsUtil.class.getClassLoader()
  69. .getResource(BAN_WORDS_LIB_FILE_NAME)
  70. .getPath();
  71. System.out.println(path);
  72. List<String> words = FileUtils.readLines(FileUtils.getFile(path));
  73. for (String w : words) {
  74. if (StringUtils.isNotBlank(w)) {
  75. //将敏感词按长度存入map
  76. banWordsList[w.length()].put(w.toLowerCase(), "");
  77. Integer index = wordIndex.get(w.substring(0, 1));
  78. //生成敏感词索引,存入map
  79. if (index == null) {
  80. index = 0;
  81. }
  82. int x = (int) Math.pow(2, w.length());
  83. index = (index | x);
  84. wordIndex.put(w.substring(0, 1), index);
  85. }
  86. }
  87. }
  88. /**
  89. * 检索敏感词
  90. * @param content
  91. * @return
  92. */
  93. public static List<String> searchBanWords(String content) {
  94. if (banWordsList == null) {
  95. try {
  96. initBanWordsList();
  97. } catch (IOException e) {
  98. throw new RuntimeException(e);
  99. }
  100. }
  101. List<String> result = new ArrayList<String>();
  102. for (int i = 0; i < content.length(); i++) {
  103. Integer index = wordIndex.get(content.substring(i, i + 1));
  104. int p = 0;
  105. while ((index != null) && (index > 0)) {
  106. p++;
  107. index = index >> 1;
  108. String sub = "";
  109. if ((i + p) < (content.length() - 1)) {
  110. sub = content.substring(i, i + p);
  111. } else {
  112. sub = content.substring(i);
  113. }
  114. if (((index % 2) == 1) && banWordsList[p].containsKey(sub)) {
  115. result.add(content.substring(i, i + p));
  116. // System.out.println("找到敏感词:"+content.substring(i,i+p));
  117. }
  118. }
  119. }
  120. return result;
  121. }
  122. public static void main(String[] args) throws IOException {
  123. String content = "含有敏感词的测试语句。";
  124. BanWordsUtil.initBanWordsList();
  125. List<String> banWordList = BanWordsUtil.searchBanWords(content);
  126. for(String s : banWordLis){
  127. System.out.println("找到敏感词:"+s);
  128. }
  129. }
  130. }

上面测试语文本里面其实没有敏感词(我也怕被屏蔽XD),测试的时候随便加入几个敏感词都能检测出来的。这样就实现了一个简易又快速的敏感词检测,当然如果有需要比较复杂的检测逻辑(比如说“弹吉他妈妈真漂亮”这样的),还是要用到分词工具把词拆分一下的。

第一次用Markdown写作,哈哈 :)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注