前言
近期在做公司历史文章敏感词检测,网安提供了一份词库。早期同学实现的方式是通过 es 中的全文索引(IK 分词)进行匹配,由于分词的特性导致了许多敏感词无法有效的被检测出来,从而导致了处罚。近期重启文章清理,这边分享一种基于前缀树 + 词库的敏感词匹配方案,查找时间复杂度在 O(n) - O(n²)之间,n 为文章长度,复杂度与敏感词数量无关。
一、新建前缀树节点类
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 敏感词前缀树节点
*
* @author omg on 2021/10/28
**/
public class SensitiveTrieNode {
/**
* 是否为单词结尾
*/
private Boolean end;
/**
* 下一个节点
*/
private Map<Character, SensitiveTrieNode> nodes;
public SensitiveTrieNode() {
this.nodes = new ConcurrentHashMap<>();
}
public Boolean getEnd() {
return end;
}
public void setEnd(Boolean end) {
this.end = end;
}
public Map<Character, SensitiveTrieNode> getNodes() {
return nodes;
}
public void setNodes(Map<Character, SensitiveTrieNode> nodes) {
this.nodes = nodes;
}
}
二、新建前缀词树查找类
import lombok.Data;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 敏感词前缀树
*
* @author omg on 2021/10/28
**/
public class SensitiveTrieTree {
private Map<Character, SensitiveTrieNode> roots;
public SensitiveTrieTree() {
roots = new ConcurrentHashMap<>();
}
/**
* 添加敏感词
*
* @param word 敏感词
*/
public void addWord(String word) {
if (word == null || word.isEmpty()) {
return;
}
Map<Character, SensitiveTrieNode> currNodes = roots;
for (int i = 0; i < word.length(); i++) {
SensitiveTrieNode node = currNodes.get(word.charAt(i));
if (node == null) {
node = new SensitiveTrieNode();
node.setEnd(i == word.length() - 1);
currNodes.put(word.charAt(i), node);
}
currNodes = node.getNodes();
}
}
/**
* 获取文本中的敏感词
* <p>
* 时间复杂度:
* n = 文本长度
* 最差 o(n^2)
* 最优 o(n)
* </p>
*
* @param txt 要查询敏感词的文本
* @return {@link List<String>}
*/
public Set<String> getWords(String txt) {
Set<String> result = new HashSet<>();
if (txt == null || txt.isEmpty()) {
return result;
}
for (int i = 0; i < txt.length(); i++) {
Map<Character, SensitiveTrieNode> currNodes = roots;
for (int j = i; j < txt.length(); j++) {
SensitiveTrieNode node = currNodes.get(txt.charAt(j));
if (node == null) {
break;
}
if (node.getEnd()) {
result.add(txt.substring(i, j + 1));
i = j;
break;
}
currNodes = node.getNodes();
}
}
return result;
}
}
三、调用测试
public static void main(String[] args) {
SensitiveTrieTree trieTree = new SensitiveTrieTree();
trieTree.addWord("新疆");
trieTree.addWord("西藏");
System.out.println(trieTree.getWords("东躲西藏,我把东西藏哪了,开辟新疆图"));
}
调用结果
四、总结
以上就是通过前缀树 + 词库实现敏感词匹配的实现方式。