最近刚好有群友问到关于敏感词过滤的问题,当时有人给出了一些办法。
1. 利用HashSet,对传多来的字符串进行比较。或者将敏感词存储到数据库或者其他地方,然后和传入的词做匹配。
2. 正则表达式匹配。
上述两个方法不用想肯定都是很慢的。后来有人说道可以利用DFA算法,因此我去研究了一下,增加自己的知识面。
参考博客:
https://www.cnblogs.com/zyguo/p/4705086.html
具体的解释不在写了,本质上是一个状态机,根据state+event --> nextState。了解过工作流的人肯定不陌生状态机这个概念的。围绕这个状态机,可以根据输入的一个词,推导出下一个词是什么。
根据参考博客,稍微修改了下实现,本质一样。
public class Check {
private static Map localMap = null;
public static void init(Set<String> keyWordSet){
localMap = new HashMap(keyWordSet.size());
String key = null;
Map nowMap = null;
Map<String, String> newWorMap = null;
for(String str : keyWordSet){
key = str;
nowMap = localMap;
for(int i = 0 ; i < key.length() ; i++){
char keyChar = key.charAt(i);
Object wordMap = nowMap.get(keyChar);
if(wordMap != null){
//如果存在该key,直接赋值
nowMap = (Map) wordMap;
}else{
//不存在则,则构建一个map,同时将isEnd设置为0,因为他不是最后一个
newWorMap = new HashMap<>();
//不是最后一个
newWorMap.put("isEnd", "0");
nowMap.put(keyChar, newWorMap);
nowMap = newWorMap;
}
if(i == key.length() - 1){
//最后一个
nowMap.put("isEnd", "1");
}
}
}
}
/**
* 版本一
* @param txt 需要查找的字符串
* @param beginIndex 查找开始位置
* @return
*/
public static Set<String> getSensitiveWordV1(String txt,int beginIndex){
//敏感词集合
Set<String> str = new HashSet<>();
char word = 0;
Map nowMap = localMap;
StringBuilder sb = new StringBuilder();
for(int i = beginIndex; i < txt.length() ; i++){
word = txt.charAt(i);
nowMap = (Map) nowMap.get(word);
if(nowMap != null){
sb.append(word); //拼接敏感词
if("1".equals(nowMap.get("isEnd"))){
str.add(sb.toString()); //保存当前敏感词
sb.setLength(0); //清除当前已保存的敏感词
}
}else {
//如果查找不到,将初始值赋值给map
nowMap = localMap;
//清除当前保存的敏感词,可能保存到一半发现不满足要求,需要清空,以便下次保存
sb.setLength(0);
}
}
return str;
}
}
getSensitiveWordV1这个称作版本一,和博客里是一样的,但是我运行时发现一点问题。
问题一:
假设现在敏感词是“中国人”,“你们”, 待检测字段是“你好啊中国人你们”,会发现只能检测出“中国人”,而“你们”却漏掉了,主要原因就是当读取到“你”这个字时,发现map此时为null了,那么原本从“中”字开始时发现此时应该是检测敏感词了,但是检测到“你”时,发现不符合敏感词规则,所以检测出敏感词是“中国人”,但是下一次循环就从“们”开始了,“你”这个字就华丽丽的溜掉了。
其实这个场景的本质就是两个敏感词联在一起,导致原本检测第一个敏感词时,本来该结束的时候,下一个字时另一个敏感词的开始,被忽略了。
修复代码:
/**
* 版本二
* @param txt 需要查找的字符串
* @param beginIndex 查找开始位置
* @return
*/
public static Set<String> getSensitiveWordV2(String txt,int beginIndex){
//敏感词集合
Set<String> str = new HashSet<>();
char word = 0;
Map nowMap = localMap;
boolean flag = false;
StringBuilder sb = new StringBuilder();
for(int i = beginIndex; i < txt.length() ; i++){
word = txt.charAt(i);
nowMap = (Map) nowMap.get(word);
if(nowMap != null){
sb.append(word); //拼接敏感词
flag = true; //标注当前正在进行敏感词过滤
if("1".equals(nowMap.get("isEnd"))){
str.add(sb.toString()); //保存当前敏感词
sb.setLength(0); //清除当前已保存的敏感词
flag = false; //标注当前敏感词过滤成功
nowMap = localMap; //过滤成功后将敏感词重新赋值给nowMap
}
}else {
if(flag){
flag = false;
//当前正在进行敏感词过滤,但是进行到一半中断了,
// 此时需要回退一位,重新判断此字符是否是另一个敏感词的开始
i--;
}
//如果查找不到,将初始值赋值给map
nowMap = localMap;
//清除当前保存的敏感词,可能保存到一半发现不满足要求,需要清空,以便下次保存
sb.setLength(0);
}
}
return str;
}
但是写着我又发现问题二了:
问题二:
假设现在敏感词是“中国人民”,“测试”,只能检测出“中国人民”和“测试”这两个词,但是如果现在有敏感词有“中国人”的话,就检测不出“中国人民”了,这个怎么解决,我暂时还没想好,只能想到针对这种情况的话,DFA应该是没法满足的,因为它是状态机,“中国人”和“中国人民”在状态机里,原本是“民”是最后一个状态,但是现在“人”变成最后一个状态了,导致只能检测到“中国人”就结束了,“民”就丢掉了。