当初找实习面试时一面试官问 一段话如何判断其中是否包含敏感词,当时经验跟能力不足,也只能想到使用正则表达式或者双重循环蛮力实现,但是肯定不能满足要求。现在工作几年了突然想起来,想想当初面试官给讲述的方法并参考网上文章加上自己的理解还是将功能做出来了。
DFA即Deterministic Finite Automaton,也就是确定有穷自动机,它是是通过event和当前的state得到下一个state,即event+state=nextstate,状态转化如下图:
{
"中": {
"isEnd": 0,
"国": {
"男": {
"人": {
"isEnd": 1
},
"isEnd": 0
},
"人": {
"isEnd": 1
},
"isEnd": 0,
"好": {
"人": {
"isEnd": 1
},
"isEnd": 0
}
}
}
}
1、在HashMap中判断是否存在“中”,如果不存在,则构建以“中”开头的树,跳至3;
2、如果存在,则取以“中”开头的树为当前HashMap,跳至1,继续遍历“国”、“人”;
3、判断该字是否是该词的最后一个字,如果是标志位(isEnd)设为1,否则设为0
/**
* 将敏感词加入HashMap
*/
private void addSensitiveWordsToHashMap(Set<String> sensitiveWords) {
sensitiveWordsMap = new HashMap(sensitiveWords.size());
String word = null;
Map childMap = null;
Map<String,String> newWordMap = null;
Iterator<String> iterator = sensitiveWords.iterator();
while (iterator.hasNext()){
//关键字
word = iterator.next();
childMap = sensitiveWordsMap;
//遍历该关键字
for (int i = 0; i < word.length();i++){
char key = word.charAt(i);
Object wordMap = childMap.get(key);
if(wordMap != null){
childMap = (Map)wordMap;
} else {
newWordMap = new HashMap<>();
newWordMap.put("isEnd", "0");
childMap.put(key, newWordMap);
//指向当前map,继续遍历
childMap = newWordMap;
}
if(i == word.length() - 1){
//最后一个
childMap.put("isEnd", "1");
}
}
}
}
以匹配“中国人”为例
1、获取以“中”开头的map,如果map为空,则不是敏感词,继续跳转到1依次遍历“国”、“人”;
2、如果不为空,则匹配标识+1,并通过isEnd判断该词匹配是否结束,跳转至3;
3、如果isEnd=1,则表示已结束,表示匹配到该敏感词,则包含敏感词,匹配标识则为敏感词长度,否则继续跳转到1依次遍历“国”、“人”
因此,“中国人”可以匹配到,而“中国坏人”则匹配不到
/**
* 校验是否包含敏感词
* @param txt 待判断文本
* @param start 起始位置
* @param matchType 匹配类型: 1 最小匹配原则;2 最大匹配原则
* @return 大于0表示包含敏感词且表示敏感词匹配长度,否则不包含
*/
private static Integer checkSensitiveWords(String txt, Integer start, Integer matchType) {
//敏感词结束标志
Boolean flag = false;
char word;
//匹配标识数默认为0
Integer matchFlag = 0;
Map childMap = sensitiveWordsMap;
System.out.println(sensitiveWordsMap);
for(int i = start;i < txt.length();i++){
word = txt.charAt(i);
childMap = (Map)childMap.get(word);
if(childMap == null){
//不存在该字打头的敏感词
break;
} else {
//匹配标识+1
matchFlag++;
if("1".equals(childMap.get("isEnd"))){
flag = true;
if(minMatchType.equals(matchType)){
//最小匹配规则则跳出,否则继续匹配
break;
}
}
}
}
if(matchFlag < 2 || !flag){
//匹配长度需大于2才为词,并且敏感词已结束
matchFlag = 0;
}
return matchFlag;
}
public static void main(String[] args) {
String txt = "当初实习面试问的一个问题就是检索一段话中是否包含敏感词,当初只能想到双重循环蛮力实现," +
"现在工作近三年突然回想起,还是完成了敏感词替换代码。虽然感觉很操蛋,但还是很牛逼,简单列举几个敏感词," +
"你妹的别给假钞!";
Boolean containsSensitiveWords = SensitiveWordsFilter.isContainsSensitiveWords(txt, 1);
System.out.println("Is contains sensitive word : " + containsSensitiveWords);
}
package SensitiveWords;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.*;
/**
* @description: 构建敏感词库
* 将敏感词从文件读取放入HashMap
* @author: zrk
* @create: 2019/1/3
*/
public class SensitiveWordsInit {
public HashMap sensitiveWordsMap;
/**
* 初始化敏感词HashMap
*/
public Map initSensitiveWords(){
try {
//读取敏感词文件
Set<String> sensitiveWordSet = readSensitiveWordsFile();
//将敏感词加入HashMap
addSensitiveWordsToHashMap(sensitiveWordSet);
} catch (Exception e){
e.printStackTrace();
}
return sensitiveWordsMap;
}
/**
* 将敏感词加入HashMap
*/
private void addSensitiveWordsToHashMap(Set<String> sensitiveWords) {
sensitiveWordsMap = new HashMap(sensitiveWords.size());
String word = null;
Map childMap = null;
Map<String,String> newWordMap = null;
Iterator<String> iterator = sensitiveWords.iterator();
while (iterator.hasNext()){
//关键字
word = iterator.next();
childMap = sensitiveWordsMap;
//遍历该关键字
for (int i = 0; i < word.length();i++){
char key = word.charAt(i);
Object wordMap = childMap.get(key);
if(wordMap != null){
childMap = (Map)wordMap;
} else {
newWordMap = new HashMap<>();
newWordMap.put("isEnd", "0");
childMap.put(key, newWordMap);
//指向当前map,继续遍历
childMap = newWordMap;
}
if(i == word.length() - 1){
//最后一个
childMap.put("isEnd", "1");
}
}
}
}
/**
* 读取敏感词文件
*/
private Set<String> readSensitiveWordsFile() {
File file = new File("D:\\SensitiveWords.txt");
InputStreamReader isReader = null;
try {
isReader = new InputStreamReader(new FileInputStream(file), "GBK");
if(file.exists() && file.isFile()){
BufferedReader br = new BufferedReader(isReader);
String s = br.readLine();
if(s != null && !"".equals(s)){
Set<String> set = new HashSet<>();
Collections.addAll(set, s.split(","));
return set;
}
} else {
System.out.println("读取文件的不存在!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(isReader != null) {
try {
isReader.close();
} catch ( Exception e){
}
}
}
return null;
}
}
package SensitiveWords;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* @description: 敏感词过滤器
* @author: zrk
* @create: 2019/1/3
*/
public class SensitiveWordsFilter {
private static Map<String, String> sensitiveWordsMap = null;
private static Integer minMatchType = 1; //最小匹配规则
static {
sensitiveWordsMap = new SensitiveWordsInit().initSensitiveWords();
}
/**
* 判断文本是否包含关键字
* @param txt 待判断文本
* @param matchType 匹配类型: 1 最小匹配原则;2 最大匹配原则
* @return true 包含;false 不包含
*/
public static Boolean isContainsSensitiveWords(String txt, Integer matchType){
if(txt == null || "".equals(txt)){
return false;
}
for(int i = 0; i < txt.length();i++){
Integer matchFlag = checkSensitiveWords(txt, i , matchType);
if(matchFlag > 0){
return true;
}
}
return false;
}
/**
* 校验是否包含敏感词
* @param txt 待判断文本
* @param start 起始位置
* @param matchType 匹配类型: 1 最小匹配原则;2 最大匹配原则
* @return 大于0表示包含敏感词且表示敏感词匹配长度,否则不包含
*/
private static Integer checkSensitiveWords(String txt, Integer start, Integer matchType) {
//敏感词结束标志
Boolean flag = false;
char word;
//匹配标识数默认为0
Integer matchFlag = 0;
Map childMap = sensitiveWordsMap;
for(int i = start;i < txt.length();i++){
word = txt.charAt(i);
childMap = (Map)childMap.get(word);
if(childMap == null){
//不存在该字打头的敏感词
break;
} else {
//匹配标识+1
matchFlag++;
if("1".equals(childMap.get("isEnd"))){
flag = true;
if(minMatchType.equals(matchType)){
//最小匹配规则则跳出,否则继续匹配
break;
}
}
}
}
if(matchFlag < 2 || !flag){
//匹配长度需大于2才为词,并且敏感词已结束
matchFlag = 0;
}
return matchFlag;
}
/**
* 获取所有敏感词
* @param txt 待判断文本
* @param matchType 匹配类型: 1 最小匹配原则;2 最大匹配原则
* @return
*/
public static Set<String> getSensitiveWords(String txt,Integer matchType){
Set<String> sensitiveWords = new HashSet<>();
for(int i = 0;i < txt.length();i++){
Integer length = checkSensitiveWords(txt, i, matchType);
if(length > 0){
sensitiveWords.add(txt.substring(i, i + length));
//循环i会+1,所以需-1
i = i + length - 1;
}
}
return sensitiveWords;
}
/**
* 替换敏感词
* @param txt 文本
* @param matchType 匹配类型: 1 最小匹配原则;2 最大匹配原则
* @param replaceStr 替换字符
* @return 处理后的文本
*/
public static String replaceSensitiveWords(String txt, Integer matchType, String replaceStr){
if(txt == null || "".equals(txt)){
return txt;
}
//获取所有敏感词
Set<String> sensitiveWords = getSensitiveWords(txt, matchType);
Iterator<String> iterator = sensitiveWords.iterator();
String replaceString = "";
while (iterator.hasNext()){
String sWord = iterator.next();
replaceString = getReplaceString(replaceStr, sWord.length());
txt = txt.replaceAll(sWord, replaceString);
}
return txt;
}
private static String getReplaceString(String replaceStr, Integer length) {
if(replaceStr == null){
replaceStr = "*";
}
StringBuffer replaceString = new StringBuffer();
for(int i = 0;i < length;i++){
replaceString.append(replaceStr);
}
return replaceString.toString();
}
}
package SensitiveWords;
/**
* @description:
* @author: zrk
* @create: 2019/1/3
*/
public class SensitiveWordsTest {
public static void main(String[] args) {
String txt = "当初实习面试问的一个问题就是检索一段话中是否包含敏感词,当初只能想到双重循环蛮力实现," +
"现在工作近三年突然回想起,还是完成了敏感词替换代码。虽然感觉很操蛋,但还是很牛逼,简单列举几个敏感词," +
"你妹的别给假钞!";
// Boolean containsSensitiveWords = SensitiveWordsFilter.isContainsSensitiveWords(txt, 1);
// System.out.println("Is contains sensitive word : " + containsSensitiveWords);
// Set sensitiveWords = SensitiveWordsFilter.getSensitiveWords(txt, 1);
// System.out.println("All sensitive words : " + sensitiveWords);
Long beginTime = System.currentTimeMillis();
System.out.println("After replace sensitive words : " + SensitiveWordsFilter.replaceSensitiveWords(txt, 1, null));
Long endTime = System.currentTimeMillis();
System.out.println("cost : " + (endTime - beginTime));
}
}
中国人,中国男人,中国好人,牛逼,操蛋,你妹的,摇头丸,白粉,冰毒,海洛因,假钞,人民币,假币
After replace sensitive words : 当初实习面试问的一个问题就是检索一段话中是否包含敏感词,当初只能想到双重循环蛮力实现,现在工作近三年突然回想起,还是完成了敏感词替换代码。虽然感觉很**,但还是很**,简单列举几个敏感词,***别给**!
cost : 5
方法参考与网上,根据自己的理解整理,代码也进行了很多参考,好的方法值得拿来分享!
参考链接:https://blog.csdn.net/chenssy/article/details/26961957