敏感詞、文字過濾是一個網站必不可少的功能,如何設計一個好的、高效的過濾算法是很是有必要的。前段時間我一個朋友(立刻畢業,接觸編程不久)要我幫他看一個文字過濾的東西,它說檢索效率很是慢。我把它程序拿過來一看,整個過程以下:讀取敏感詞庫、若是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))三個方法