在网上看到好多的敏感词检测,发现都是在推荐某某算法,但是敏感词全是利用文本去存放。在项目中不能很好的进行维护和管理(个人看法)。
本文的敏感词的检测方式还是DFA算法检测,不过敏感词存放地址放入了Redis的中,方便维护和更新。
1.使用redis进行敏感词存储
将文本中的敏感词读取出来然后存放到redis的中。使用设置类型进行存储(去重)
代码略...
2.使用二叉树进行敏感词分类
跟网上大部分的代码一致(不过本文中是使用单例模式进行调用,只在第一次调用时进行redis读取操作存放到项目中)
package com.cctv.aisys.utils.words;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class BadWordUtil {
public static Set<String> words;
public static Map<String, String> wordMap;
public static int minMatchTYpe = 1; // 最小匹配规则
public static int maxMatchType = 2; // 最大匹配规则
private static BadWordUtil badWordUtil;
private final Logger logger = LoggerFactory.getLogger(BadWordUtil.class);
private BadWordUtil() {}
/**
* 生成敏感词缓存
* @param jedisPool
*/
private BadWordUtil(JedisPool jedisPool) {
logger.info("正在生成敏感词缓存");
BadWordUtil2.words = readTxtByLine(jedisPool);
addBadWordToHashMap(BadWordUtil2.words);
logger.info("敏感词缓存成功");
}
public static BadWordUtil getBadWordUtil(JedisPool jedisPool) {
if (badWordUtil == null) {
badWordUtil = new BadWordUtil(jedisPool);
}
return badWordUtil;
}
private Set<String> readTxtByLine(JedisPool jedisPool) {
Set<String> keyWordSet = new HashSet<String>();
Jedis jedis = jedisPool.getResource();
keyWordSet = jedis.smembers("sensitive");
return keyWordSet;
}
/**
* 检查文字中是否包含敏感字符,检查规则如下:<br>
*
* @param txt
* @param beginIndex
* @param matchType
* @return,如果存在,则返回敏感词字符的长度,不存在返回0
* @version 1.0
*/
@SuppressWarnings({ "rawtypes" })
private int checkBadWord(String txt, int beginIndex, int matchType) {
boolean flag = false; // 敏感词结束标识位:用于敏感词只有1位的情况
int matchFlag = 0; // 匹配标识数默认为0
char word = 0;
Map nowMap = wordMap;
for (int i = beginIndex; i < txt.length(); i++) {
word = txt.charAt(i);
nowMap = (Map) nowMap.get(word); // 获取指定key
if (nowMap != null) { // 存在,则判断是否为最后一个
matchFlag++; // 找到相应key,匹配标识+1
if ("1".equals(nowMap.get("isEnd"))) { // 如果为最后一个匹配规则,结束循环,返回匹配标识数
flag = true; // 结束标志位为true
if (minMatchTYpe == matchType) { // 最小规则,直接返回,最大规则还需继续查找
break;
}
}
} else { // 不存在,直接返回
break;
}
}
/*
* “粉饰”匹配词库:“粉饰太平”竟然说是敏感词 “个人”匹配词库:“个人崇拜”竟然说是敏感词 if(matchFlag < 2 && !flag){
* matchFlag = 0; }
*/
if (!flag) {
matchFlag = 0;
}
return matchFlag;
}
/**
* 判断文字是否包含敏感字符
*
* @param txt
* 文字
* @param matchType
* 匹配规则 1:最小匹配规则,2:最大匹配规则
* @return 若包含返回true,否则返回false
* @version 1.0
*/
@SuppressWarnings("unused")
private boolean isContaintBadWord(String txt, int matchType) {
boolean flag = false;
for (int i = 0; i < txt.length(); i++) {
int matchFlag = checkBadWord(txt, i, matchType); // 判断是否包含敏感字符
if (matchFlag > 0) { // 大于0存在,返回true
flag = true;
}
}
return flag;
}
/**
* 替换敏感字字符
*
* @param txt
* @param matchType
* @param replaceChar
* 替换字符,默认*
* @version 1.0
*/
@SuppressWarnings("unused")
private String replaceBadWord(String txt, int matchType, String replaceChar) {
String resultTxt = txt;
Set<String> set = getBadWord(txt, matchType); // 获取所有的敏感词
Iterator<String> iterator = set.iterator();
String word = null;
String replaceString = null;
while (iterator.hasNext()) {
word = iterator.next();
replaceString = getReplaceChars(replaceChar, word.length());
resultTxt = resultTxt.replaceAll(word, replaceString);
}
return resultTxt;
}
/**
* 获取文字中的敏感词
*
* @param txt
* 文字
* @param matchType
* 匹配规则 1:最小匹配规则,2:最大匹配规则
* @return
* @version 1.0
*/
private Set<String> getBadWord(String txt, int matchType) {
Set<String> sensitiveWordList = new HashSet<String>();
for (int i = 0; i < txt.length(); i++) {
int length = checkBadWord(txt, i, matchType); // 判断是否包含敏感字符
if (length > 0) { // 存在,加入list中
sensitiveWordList.add(txt.substring(i, i + length));
i = i + length - 1; // 减1的原因,是因为for会自增
}
}
return sensitiveWordList;
}
/**
* 获取替换字符串
*
* @param replaceChar
* @param length
* @return
* @version 1.0
*/
private String getReplaceChars(String replaceChar, int length) {
String resultReplace = replaceChar;
for (int i = 1; i < length; i++) {
resultReplace += replaceChar;
}
return resultReplace;
}
/**
* TODO 将我们的敏感词库构建成了一个类似与一颗一颗的树,这样我们判断一个词是否为敏感词时就大大减少了检索的匹配范围。
*
* @param keyWordSet
* 敏感词库
* @author yqwang0907
* @date 2018年2月28日下午5:28:08
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private void addBadWordToHashMap(Set<String> keyWordSet) {
wordMap = new HashMap(keyWordSet.size()); // 初始化敏感词容器,减少扩容操作
String key = null;
Map nowMap = null;
Map<String, String> newWorMap = null;
// 迭代keyWordSet
Iterator<String> iterator = keyWordSet.iterator();
while (iterator.hasNext()) {
key = iterator.next(); // 关键字
nowMap = wordMap;
for (int i = 0; i < key.length(); i++) {
char keyChar = key.charAt(i); // 转换成char型
Object wordMap = nowMap.get(keyChar); // 获取
if (wordMap != null) { // 如果存在该key,直接赋值
nowMap = (Map) wordMap;
} else { // 不存在则,则构建一个map,同时将isEnd设置为0,因为他不是最后一个
newWorMap = new HashMap<String, String>();
newWorMap.put("isEnd", "0"); // 不是最后一个
nowMap.put(keyChar, newWorMap);
nowMap = newWorMap;
}
if (i == key.length() - 1) {
nowMap.put("isEnd", "1"); // 最后一个
}
}
}
}
/**
* 敏感词检测调用接口
* @param content 检测文本
* @return 检测结果
*/
public JSONObject check(String content) {
JSONObject json = new JSONObject();
long start = System.currentTimeMillis();
Set<String> set = BadWordUtil2.getBadWord(content, 5);
long end = System.currentTimeMillis();
json.put("keyNum", BadWordUtil2.wordMap.size());
json.put("result", set);
json.put("time", end - start);
boolean flag = true;
if (set != null && set.size() > 0) {
flag = false;
}
json.put("flag", flag);
return json;
}
/**
* 清空敏感词
*/
public void clear() {
badWordUtil = null;
logger.info("敏感词缓存已清空");
}
3.输入内容进行敏感词检测
@Test
public void main1() {
String string = "一直以为写小说是作家的事,普通如我这样的业余写作爱好者,写小说那只能是梦想。六合彩";
BadWordUtil badWordUtil = BadWordUtil.getBadWordUtil(jedisPool);
JSONObject json = badWordUtil.check(string);
System.out.println(json);
//输出 :{"result":["六合彩"],"flag":false,"keyNum":1701,"time":1}
4.向redis中添加敏感词并更新程序中的敏感词
单例模式下,销毁当前对象,然后再生成一个新对象,实现敏感词的更新。
Jedis resource = jedisPool.getResource();
Long sadd = resource.sadd("sensitive", "大乐透七彩");
System.out.println(sadd);
BadWordUtil badWordUtil = BadWordUtil.getBadWordUtil(jedisPool);
badWordUtil.clear(); //单例模式 销毁对象,让他重新new一个对象,实现敏感词的更新
//输出1
24万的敏感词 加载一次只需要4秒。检测时间1-2毫秒。性能是相当的快,而且方便维护与管理