使用Trie樹實現網站對使用者輸入的敏感詞打碼
阿新 • • 發佈:2018-12-24
使用Trie樹實現網站對使用者輸入的敏感詞打碼
什麼是Trie樹?
Trie樹,又稱單詞查詢樹,Trie樹,是一種樹形結構,是一種雜湊樹的變種。典型應用是用於統計,排序和儲存大量的字串(但不僅限於字串),所以經常被搜尋引擎系統用於文字詞頻統計。它的優點是:利用字串的公共字首來減少查詢時間,最大限度地減少無謂的字串比較,查詢效率比雜湊樹高。
Trie樹的核心思想是空間換時間,利用字串的公共字首來降低查詢時間的開銷以達到提高效率的目的。
使用Trie樹資料結構可以匹配多個關鍵詞,速度快。Trie樹的最壞空間複雜度為O(m^n),最壞時間複雜度為O(n)。從而可以利用該資料結構把一段文字中敏感詞替換為***或者刪除掉
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
@Service
public class SensitiveService implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(SensitiveService.class);
private TrieNode rootNode = new TrieNode();
public static void main(String[] args) {
SensitiveService s = new SensitiveService();
s.addWord("色情");
s.addWord("賭博");
String test = "你好啊色*情傢伙,竟然賭博啊,你是個壞人!";
System.out.println(s.filter(test));
}
public String filter(String text) {
StringBuilder sb = new StringBuilder();
if (StringUtils.isEmpty(text)) {
return text;
}
String replace = "***";
TrieNode curNode = rootNode;
int begin = 0;
int position = 0;
while (position < text.length()) {
char c = text.charAt(position);
//如果不是東亞文字,如空格,則跳過在trie樹中匹配
if (isSymbol(c)) {
if (curNode == rootNode) {
sb.append(c);
++begin;
}
++position;
continue;
}
curNode = curNode.getSubNode(c);
/**
* 如果curNode等於Null,則說明在trie樹中找不到下個字元,這個詞不會是敏感詞
* 換句話說,trie樹中沒有這個子節點,說明begin開頭的字串不是敏感詞,
* 把begin這個字元加入過濾後的字串中,同時字首樹指標指回開頭
*/
if (curNode == null) {
sb.append(text.charAt(begin));
position = begin + 1;
begin = position;
curNode = rootNode;
} else if (curNode.isKeywordEnd()) {
//發現一個敏感詞
sb.append(replace);
position = position + 1;
begin = position;
curNode = rootNode;
} else {
++position;
}
}
sb.append(text.substring(begin));
return sb.toString();
}
@Override
public void afterPropertiesSet() throws Exception {
try {
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("SensitiveWords.txt");
InputStreamReader reader = new InputStreamReader(is);
BufferedReader bufferedReader = new BufferedReader(reader);
String lineTxt;
while ((lineTxt = bufferedReader.readLine()) != null) {
addWord(lineTxt.trim());
}
reader.close();
} catch (Exception e) {
logger.error("讀取過濾的敏感詞檔案失敗" + e.getMessage());
}
}
//構建trie樹,在字首樹中新增敏感詞
private void addWord(String lineTxt) {
TrieNode curNode = rootNode;
for (int i = 0; i < lineTxt.length(); ++i) {
Character c = lineTxt.charAt(i);
TrieNode node = curNode.getSubNode(c);
if (node == null) {
node = new TrieNode();
curNode.addSubNode(c, node);
}
curNode = node;
if (i == lineTxt.length() - 1) {
curNode.setKeywordEnd(true);
}
}
}
private class TrieNode {
private boolean end = false;
//子節點
private Map<Character, TrieNode> subNodes = new HashMap<>();
public void addSubNode(Character key, TrieNode node) {
subNodes.put(key, node);
}
TrieNode getSubNode(Character key) {
return subNodes.get(key);
}
boolean isKeywordEnd() {
return end;
}
void setKeywordEnd(boolean end) {
this.end = end;
}
}
//返回true表示不是東亞文字
private boolean isSymbol(char c) {
int i = (int) c;
//東亞文字0x2E80-0x9FFF
return !CharUtils.isAsciiAlphanumeric(c) && (i < 0x2E80 || i > 0x9FFF);
}
}
該程式執行結果如下圖: