一、DFA 算法簡介
在實現文字過濾的算法中,DFA是唯一比較好的實現算法。
DFA 全稱為:Deterministic Finite Automaton,即確定有窮自動機。其特征為:有一個有限狀態集合和一些從一個狀態通向另一個狀態的邊,每條邊上標記有一個符號,其中一個狀態是初態,某些狀態是終態。但不同於不確定的有限自動機,DFA 中不會有從同一狀態出發的兩條邊標志有相同的符號。
簡單點說就是,它是是通過 event 和當前的 state 得到下一個 state,即 event + state= nextstate。理解為系統中有多個節點,通過傳遞進入的 event,來確定走哪個路由至另一個節點,而節點是有限的。
二、DEA 算法實踐敏感詞過濾
1. 敏感詞庫構造
以王八蛋和王八羔子兩個敏感詞來進行描述,首先構建敏感詞庫,該詞庫名稱為SensitiveMap,這兩個詞的二叉樹構造為:
用 hash 表構造為:
{
"王":{
"isEnd":"0",
"八":{
"羔":{
"子":{
"isEnd":"1"
},
"isEnd":"0"
},
"isEnd":"0",
"蛋":{
"isEnd":"1"
}
}
}
}
怎么用代碼實現這種數據結構呢?
/**
* 讀取敏感詞庫,將敏感詞放入HashSet中,構建一個DFA算法模型
*
* @param keyWordSet 敏感詞庫
*/
public Map<String, Object> addSensitiveWordToHashMap(Set<String> keyWordSet) {
//初始化敏感詞容器,減少擴容操作
Map<String, Object> map = new HashMap(Math.max((int) (keyWordSet.size() / .75f) + 1, 16));
//迭代keyWordSet
for (String aKeyWordSet : keyWordSet) {
Map nowMap = map;
for (int i = 0; i < aKeyWordSet.length(); i++) {
//轉換成char型
char keyChar = aKeyWordSet.charAt(i);
//獲取
Object wordMap = nowMap.get(keyChar);
//如果存在該key,直接賦值
if (wordMap != null) {
nowMap = (Map) wordMap;
} else { //不存在則,則構建一個map,同時將isEnd設置為0
Map<String, String> newWorMap = new HashMap<>(3);
newWorMap.put("isEnd", "0");
nowMap.put(keyChar, newWorMap);
nowMap = newWorMap;
}
//判斷最后一個
if (i == aKeyWordSet.length() - 1) {
nowMap.put("isEnd", "1");
}
}
}
return map;
}
2. 敏感詞過濾
以上面例子構造出來的 SensitiveMap 為敏感詞庫進行示意,假設這里輸入的關鍵字為:王八不好,流程圖如下:
怎么用代碼實現這個流程圖邏輯呢?
/**
* 查找字符串中是否包含敏感字符
*
* @param txt 輸入的字符串
* @return 如果存在,則返回敏感字符串;不存在,則返回空字符串
*/
public static String findSensitiveWord(String txt) {
SensitiveWordInit sensitiveWordInit = SpringContextHolder.getBean(SensitiveWordInit.class);
Map<String, Object> sensitiveWordMap = sensitiveWordInit.getSensitiveWordMap();
StringBuilder sensitiveWord = new StringBuilder();
// 敏感詞結束標志位,表示匹配到了最后一位
boolean flag = false;
for (int i = 0; i < txt.length(); i++) {
char word = txt.charAt(i);
// 獲取指定 key
sensitiveWordMap = (Map) sensitiveWordMap.get(word);
// 不存在,直接返回沒有敏感詞
if (sensitiveWordMap == null) {
break;
}
//存在,存儲該敏感詞,並判斷是否為最后一個
sensitiveWord.append(word);
//如果為最后一個匹配規則,結束循環
if ("1".equals(sensitiveWordMap.get("isEnd"))) {
flag = true;
break;
}
}
// 表示匹配到了完整敏感詞
if (flag == true) {
return sensitiveWord.toString();
}
return "";
}
三、優化思路
對於“王*八&&蛋”這樣的詞,中間填充了無意義的字符來混淆,在我們做敏感詞搜索時,同樣應該做一個無意義詞的過濾,當循環到這類無意義的字符時進行跳過,避免干擾。