敏感词、文字过滤是一个网站必不可少的功能,如何设计一个好的、高效的过滤算法是很是有必要的。前段时间我一个朋友(立刻毕业,接触编程不久)要我帮他看一个文字过滤的东西,它说检索效率很是慢。我把它程序拿过来一看,整个过程以下:读取敏感词库、若是HashSet集合中,获取页面上传文字,而后进行匹配。我就想这个过程确定是很是慢的。对于他这个没有接触的人来讲我想也只能想到这个,更高级点就是正则表达式。可是很是遗憾,这两种方法都是不可行的。固然,在我意识里没有我也没有认知到那个算法能够解决问题,可是Google知道!java
在实现文字过滤的算法中,DFA是惟一比较好的实现算法。DFA即Deterministic Finite Automaton,也就是肯定有穷自动机,它是是经过event和当前的state获得下一个state,即event+state=nextstate。下图展现了其状态的转换正则表达式
在这幅图中大写字母(S、U、V、Q)都是状态,小写字母a、b为动做。经过上图咱们能够看到以下关系算法
a b b
S -----> U S -----> V U -----> V编程
在实现敏感词过滤的算法中,咱们必需要减小运算,而DFA在DFA算法中几乎没有什么计算,有的只是状态的转换。工具
参考文献:http://www.iteye.com/topic/336577测试
在Java中实现敏感词过滤的关键就是DFA算法的实现。首先咱们对上图进行剖析。在这过程当中咱们认为下面这种结构会更加清晰明了。网站
同时这里没有状态转换,没有动做,有的只是Query(查找)。咱们能够认为,经过S query U、V,经过U query V、P,经过V query U P。经过这样的转变咱们能够将状态的转换转变为使用Java集合的查找。spa
诚然,加入在咱们的敏感词库中存在以下几个敏感词:日本人、日本鬼子、毛.泽.东。那么我须要构建成一个什么样的结构呢?.net
首先:query 日 ---> {本}、query 本 --->{人、鬼子}、query 人 --->{null}、query 鬼 ---> {子}。形以下结构:设计
下面咱们在对这图进行扩展:
这样咱们就将咱们的敏感词库构建成了一个相似与一颗一颗的树,这样咱们判断一个词是否为敏感词时就大大减小了检索的匹配范围。好比咱们要判断日本人,根据第一个字咱们就能够确认须要检索的是那棵树,而后再在这棵树中进行检索。
可是如何来判断一个敏感词已经结束了呢?利用标识位来判断。
因此对于这个关键是如何来构建一棵棵这样的敏感词树。下面我已Java中的HashMap为例来实现DFA算法。具体过程以下:
日本人,日本鬼子为例
一、在hashMap中查询“日”看其是否在hashMap中存在,若是不存在,则证实已“日”开头的敏感词还不存在,则咱们直接构建这样的一棵树。跳至3。
二、若是在hashMap中查找到了,代表存在以“日”开头的敏感词,设置hashMap = hashMap.get("日"),跳至1,依次匹配“本”、“人”。
三、判断该字是否为该词中的最后一个字。如果表示敏感词结束,设置标志位isEnd = 1,不然设置标志位isEnd = 0;
程序实现以下:
[java] view plaincopyprint?
/**
* 读取敏感词库,将敏感词放入HashSet中,构建一个DFA算法模型:<br>
* 中 = {
* isEnd = 0
* 国 = {<br>
* isEnd = 1
* 人 = {isEnd = 0
* 民 = {isEnd = 1}
* }
* 男 = {
* isEnd = 0
* 人 = {
* isEnd = 1
* }
* }
* }
* }
* 五 = {
* isEnd = 0
* 星 = {
* isEnd = 0
* 红 = {
* isEnd = 0
* 旗 = {
* isEnd = 1
* }
* }
* }
* }
* @author chenming
* @date 2014年4月20日 下午3:04:20
* @param keyWordSet 敏感词库
* @version 1.0
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private void addSensitiveWordToHashMap(Set<String> keyWordSet) {
sensitiveWordMap = 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 = sensitiveWordMap;
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"); //最后一个
}
}
}
}
运行获得的hashMap结构以下:
{五={星={红={isEnd=0, 旗={isEnd=1}}, isEnd=0}, isEnd=0}, 中={isEnd=0, 国={isEnd=0, 人={isEnd=1}, 男={isEnd=0, 人={isEnd=1}}}}}
敏感词库咱们一个简单的方法给实现了,那么如何实现检索呢?检索过程无非就是hashMap的get实现,找到就证实该词为敏感词,不然不为敏感词。过程以下:假如咱们匹配“中国人民万岁”。
一、第一个字“中”,咱们在hashMap中能够找到。获得一个新的map = hashMap.get("")。
二、若是map == null,则不是敏感词。不然跳至3
三、获取map中的isEnd,经过isEnd是否等于1来判断该词是否为最后一个。若是isEnd == 1表示该词为敏感词,不然跳至1。
经过这个步骤咱们能够判断“中国人民”为敏感词,可是若是咱们输入“中国女人”则不是敏感词了。
[java] view plaincopyprint?
/**
* 检查文字中是否包含敏感字符,检查规则以下:<br>
* @author chenming
* @date 2014年4月20日 下午4:31:03
* @param txt
* @param beginIndex
* @param matchType
* @return,若是存在,则返回敏感词字符的长度,不存在返回0
* @version 1.0
*/
@SuppressWarnings({ "rawtypes"})
public int CheckSensitiveWord(String txt,int beginIndex,int matchType){
boolean flag = false; //敏感词结束标识位:用于敏感词只有1位的状况
int matchFlag = 0; //匹配标识数默认为0
char word = 0;
Map nowMap = sensitiveWordMap;
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(SensitivewordFilter.minMatchTYpe == matchType){ //最小规则,直接返回,最大规则还需继续查找
break;
}
}
}
else{ //不存在,直接返回
break;
}
}
if(matchFlag < 2 && !flag){
matchFlag = 0;
}
return matchFlag;
}
在文章末尾我提供了利用Java实现敏感词过滤的文件下载。下面是一个测试类来证实这个算法的效率和可靠性。
[java] view plaincopyprint?
public static void main(String[] args) {
SensitivewordFilter filter = new SensitivewordFilter();
System.out.println("敏感词的数量:" + filter.sensitiveWordMap.size());
String string = "太多的伤感情怀也许只局限于饲养基地 荧幕中的情节,主人公尝试着去用某种方式渐渐的很潇洒地释自杀指南怀那些本身经历的伤感。"
+ "而后法.轮.功 咱们的扮演的角色就是跟随着主人公的喜红客联盟 怒哀乐而过于牵强的把本身的情感也附加于银幕情节中,而后感动就流泪,"
+ "难过就躺在某一我的的怀里尽情的阐述心扉或者手机卡复制器一我的一杯红酒一部电影在夜三.级.片 深人静的晚上,关上电话静静的发呆着。";
System.out.println("待检测语句字数:" + string.length());
long beginTime = System.currentTimeMillis();
Set<String> set = filter.getSensitiveWord(string, 1);
long endTime = System.currentTimeMillis();
System.out.println("语句中包含敏感词的个数为:" + set.size() + "。包含:" + set);
System.out.println("总共消耗时间为:" + (endTime - beginTime));
}
运行结果:
从上面的结果能够看出,敏感词库有771个,检测语句长度为184个字符,查出6个敏感词。总共耗时1毫秒。可见速度仍是很是可观的。
下面提供两个文档下载:
Desktop.rar(http://pan.baidu.com/s/1o66teGU)里面包含两个Java文件,一个是读取敏感词库(SensitiveWordInit),一个是敏感词工具类(SensitivewordFilter),里面包含了判断是否存在敏感词(isContaintSensitiveWord(String txt,int matchType))、获取敏感词(getSensitiveWord(String txt , int matchType))、敏感词替代(replaceSensitiveWord(String txt,int matchType,String replaceChar))三个方法