您的位置:首页 > 编程语言 > Java开发

JAVA中的DFA算法构建敏感词树,从0开始!

2017-12-05 15:00 525 查看
前一阵做项目涉及到敏感词检测问题,因为这个模块要求不是很高,应用场景就是用户在文章下面评论,或者发布留言等需要管理员进行审核,所以要在给管理员审核的页面对评论内容中的敏感词标红、判断内容是否有敏感词等,这样能让管理员快速审核。

因为我这个敏感词模块涉及到的东西不是很多,所以做的可能比较简单,粗糙,第一次写博客,请谅解!有什么建议欢迎提出来!

下面说一下我的构建过程:

首先从第一步建表开始

项目需求是需要对敏感词有两种应用方式,一种是阻止,一种是替换。这两种方式不一定适用于所有模块,现在是针对留言、评论等,但是以后可能会涉及到别的模块。但是阻止的优先级是比替换高的。所以我当初的设想就是,表字段中有两个字段存该敏感词是阻止哪个模块,替换哪个模块,如果有需要自动代理,不需要人工审核的,只需要拿内容来,先进行检测,检测出敏感词,再拿敏感词去数据库取对象,判断该敏感词对于正在应用的模块是敏感还是替换,首先是要进行敏感词匹配。

附上我的SQL:

CREATE TABLE `base_sys_sensitive` (
`ID` int(11) unsigned NOT NULL AUTO_INCREMENT,
`NAME` varchar(200) DEFAULT NULL COMMENT '敏感词',
`REPLACENAME` varchar(200) DEFAULT NULL COMMENT '替换词',
`PREVENTOBJ` varchar(100) DEFAULT NULL COMMENT '阻止对象(适用范围)',
`REPLACEOBJ` varchar(100) DEFAULT NULL COMMENT '替换对象(适用范围)',
`CREATEUSER` int(11) unsigned DEFAULT NULL,
`CREATETIME` timestamp NULL DEFAULT NULL,
`UPDATEUSER` int(11) unsigned DEFAULT NULL,
`UPDATETIME` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`DELFLAG` tinyint(1) unsigned NOT NULL DEFAULT '0',
`REMARK` varchar(200) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`ID`)
) ENGINE=MyISAM AUTO_INCREMENT=250596 DEFAULT CHARSET=utf8;


然后就是POJO创建,这里不多说,接下来说敏感词树的构建问题:


根据我的大致计算,4万条敏感词加载到缓存其实占用的内存还是很小的,实测4万条敏感词树构建大概200ms以内,足够满足我的需求;

首先建立一个敏感词工具类:

这棵树因为是要在项目启动的时候进行初始化,在类中声明一个静态的map;

然后下一步就是用sensitiveService去数据库一次拿到所有的敏感词;

为防止数据库有脏数据,导致有重复敏感词(新增敏感词的时候,名称有唯一性检测,只是为了防止脏数据),这里先用set去重

private static Set<String> readSensitiveWordFile(){
Set<String> set = null;
set = new HashSet<String>();
List<Sensitive> sensitiveList = sensitiveService.queryAll();
for(Sensitive sensitive : sensitiveList){
set.add(sensitive.getName());
}
return set;
}
到这里的时候整个数据库中的敏感词都已经拿到了;

下一步就是初始化敏感词树;

@SuppressWarnings({ "rawtypes"})
private static Map addSensitiveWordToHashMap(Set<String> keyWordSet) {
Map sensitiveWordMap = new HashMap();
String key = null;
// 迭代keySet
Iterator<String> iterator = keyWordSet.iterator();
while(iterator.hasNext()){
key = iterator.next();
addSensitiveWordToHashMap(sensitiveWordMap, key);
}
return sensitiveWordMap;
}
这个方法主要是对敏感词set进行迭代,一条条的插入到树中;

下面看下addSensitiveWordToHashMap()方法,这个方法是插入敏感词方法;

@SuppressWarnings({ "rawtypes", "unchecked" })
private static void addSensitiveWordToHashMap(Map map, String sensitive){
Map<String, String> newWorMap = null;
for(int i = 0 ; i < sensitive.length() ; i++){
// 转换成char型
char keyChar = sensitive.charAt(i);
Object wordMap = map.get(keyChar);
// 如果存在该key,直接赋值
if (wordMap != null) {
map = (Map) wordMap;
}else {
// 不存在就构建一个map,同时将isEnd设置为0
newWorMap = new HashMap<String,String>();
// 不是最后一个
newWorMap.put("isEnd", "0");
map.put(keyChar, newWorMap);
map = newWorMap;
}
if(i == sensitive.length() - 1){
// 最后一个
map.put("isEnd", "1");
}
}
}
这个我的敏感词树就一条条的构建成功了;

当插入一条敏感词的时候,比如“小明是狗”,那么会先去map中get小,不存在就构建一个map放这个小,标识该词还没有结束,如果存在,就去小对应的map中get明以此类推,当到狗的时候,标识该词已经结束了;添加isEend为1;

那么我这里为什么要分好几个方法写呢,主要是为了项目需求,因为我们敏感词是有个管理的页面,管理员可以对某个敏感词进行添加,修改,删除操作的,所以当管理员进行这些操作的时候,这个树肯定也是要随机应变的,我这里的做法就是对外提供两个公开方法,一个添加敏感词,一个重置敏感词树,  那么在新增敏感词的时候我只需要对树中进行添加一条就行了,  删除和修改,就重置一下敏感词树(反正咱们构建时间也不长—_—)......

先来看一下对外封装的添加敏感词方法:

public static void addSensitiveWordToHashMap(String sensitive){
if(sensitiveWordMap == null){
sensitiveWordMap = addSensitiveWordToHashMap(readSensitiveWordFile());
}
addSensitiveWordToHashMap(sensitiveWordMap, sensitive);
}
再来看一下重新构建树的方法:

public static void resetSensitiveWordTree(){
sensitiveWordMap = addSensitiveWordToHashMap(readSensitiveWordFile());
}
重新构建就是重新去数据库取一次,然后构建;

树构建好了,那么下一步就是检测了:

对外提供方法有:1、该字符串是否存在敏感,2、找出字符串中所有的敏感词,3、替换字符串中的所有敏感词

他们都是基于一个方法,就是敏感词检测方法:

@SuppressWarnings({ "rawtypes"})
public static int checkSensitiveWord(String txt, int beginIndex){
if(StringUtils.isBlank(txt)){
return IConstant.ZERO;
}
if(sensitiveWordMap == null){
sensitiveWordMap = addSensitiveWordToHashMap(readSensitiveWordFile());
}
//防止敏感词只有1个
boolean flag = false;
int matchFlag = 0;
char word = 0;
Map nowMap = sensitiveWordMap;
for(int i = beginIndex; i < txt.length() ; i++){
word = txt.charAt(i);
// 获取指定key
nowMap = (Map) nowMap.get(word);
// 存在就判断是否是最后一个
if (nowMap != null) {
// 找到相应key,匹配标识+1
matchFlag++;
// 如果为最后一个匹配规则,结束循环,返回匹配标识数
if ("1".equals(nowMap.get("isEnd"))) {
// 结束标志位为true
flag = true;
}
}else {
// 不存在,直接返回
break;
}
}
// 长度必须大于等于1
if (matchFlag < 2 || !flag) {
matchFlag = 0;
}
return matchFlag;
}


这个方法返回的是该字符串中出现敏感词的起始位置;

然后封装一层,判断字符串中是否有敏感词:

public static boolean isContaintSensitiveWord(String txt){
if(StringUtils.isBlank(txt)){
return false;
}
boolean flag = false;
for(int i = 0 ; i < txt.length() ; i++){
// 判断是否包含敏感字符
int matchFlag = checkSensitiveWord(txt, i);
// 大于0存在,返回true

b1ed
if (matchFlag > 0) {
flag = true;
}
}
return flag;
}

还一个方法就是获取字符串中所有的敏感词:

public static Set<String> getSensitiveWord(String txt) {
if(StringUtils.isBlank(txt)){
return null;
}
Set<String> sensitiveWordList = new HashSet<String>();
for(int i = 0 ; i < txt.length() ; i++){
// 判断是否包含敏感字符
int length = checkSensitiveWord(txt, i);
// 存在就加入set中
if (length > 0) {
sensitiveWordList.add(txt.substring(i, i+length));
// 减1因为for会自增
i = i + length - 1;
}
}
return sensitiveWordList;
}
这里的做法就是对字符串进行循环,找到一个敏感词就截掉,再循环,一直到没有敏感词,这里用set是防止检测到重复敏感词;

还有一个替换敏感词方法:

public static String replaceSensitiveWord(String txt) {
if(StringUtils.isBlank(txt)){
return null;
}
String resultTxt = txt;
// 获取所有的敏感词
Set<String> set = getSensitiveWord(txt);
Iterator<String> iterator = set.iterator();
String word = null;
String replaceString = null;
while (iterator.hasNext()) {
word = iterator.next();
replaceString = getReplaceChars(word);
resultTxt = resultTxt.replaceAll(word, replaceString);
}
return resultTxt;
}
这里会用到一个getReplaceChars()方法,这个方法是获取敏感词对于的替换词,因为我的需求是要替换词准确到某个词,所有敏感词的替换词都对应的存在数据库;

下面也贴一下这个方法吧

public static String getReplaceChars (String name){
if(StringUtils.isBlank(name)){
return null;
}
Sensitive sensitive = new Sensitive();
sensitive.setName(name);
sensitive = sensitiveService.queryOne(sensitive);
if(sensitive != null){
String replaceTxt = sensitive.getReplacename();
if(StringUtils.isNotBlank(replaceTxt)){
return replaceTxt;
}
}
return null;
}
这里其实就是一个拿敏感词去数据库取替换词的过程;

到此整个工具类就算是能对外提供使用了,只需要在数据库插入敏感词就能用了;

做的比较粗糙,因为时间问题,也比较赶,可能以后需求还会改;

当然这个也是有缺陷的!

缺陷就是:1、空格也会列入检测范围,就是某个词可能最后加上空格也会被构建到树中,这样有好处,也有坏处

   2、没有引入距离概念,比如;词是“小明是狗”,  那么“小明.是狗”、“小明     是 狗 ”。。。。。等等这样的都不会被检测出来,所以有很大的局限性,请根据自己的需求来判断!

   3、。。。。。。。还有其他的我就不多说了。个人理解   嘿嘿~~~~~~~

新手第一次写博客~~~,如有什么技术的语言、或者代码纰漏,请指正!!!  但是别喷我~~~喷我我会很伤心~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息