前缀树
• 根节点不包含字符,除根节点外每一个节点都只包含一个字符
• 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
• 每个节点的所有子节点包含的字符都不相同
假设有字符串abcdefghijk,敏感词设置为bc,be,def
思路:
(1)将敏感词建立前缀树,每一个敏感词的最后一个字都做一个标记。如图:
(2)有三个变量begin,position,tempNode;
首先,begin指向a,position也指向a,tempNode指向树的根节点root,判断tempNode下是否有a节点,此时tempNode下无a节点,说明a不是敏感词,
begin指向下一位b,position也指向b,此时根节点下有b的节点,那么position指向下一位c,begin不动,tempNode指向b节点,当前节点下有c的节点,并且c有黄色的标志,说明从begin至position位置的字符串(即bc)为敏感词,可以采用替换的方法替换敏感词,也可以直接去除敏感词;
如此反复;
代码实现:
public class SensitiveService{
private class TrieNode{
//key下一个字符,value是对应的节点
private Map<Character,TrieNode> subNodes = new HashMap<Character,TrieNode>();
//是否是关键词的结尾 true关键词的终结,false继续
private boolean end = false;
// 向指定位置添加节点树
public void addSubNode(Character key, TrieNode trieNode){
subNodes.put(key,trieNode);
}
// 获取下个节点
public TrieNode getSubNode(Character key){
return subNodes.get(key);
}
public void setKeyWordEnd(boolean end){
this.end = end;
}
public boolean isKeyWordEnd(){
return this.end;
}
public int getSubNodeCount() {
return subNodes.size();
}
}
//根节点
private TrieNode rootNode = new TrieNode();
/**
* 判断是否是一个符号
*/
private boolean isSymbol(Character c){
int ci = (int)c;
//ox2E80-ox9FFF 东亚文字范围
return !CharUtils.isAsciiAlphanumeric(c) && (ci < 0x2E80 || ci > 0x9FFF);
}
//建立前缀树,字典树
private void addWord(String text){
TrieNode tempNode = rootNode;
//循环每个字节
for (int i = 0; i < text.length(); i++){
Character c = text.charAt(i);
//过滤空格
if (isSymbol(c)){
continue;
}
TrieNode node = tempNode.getSubNode(c);
if (node == null){
node = new TrieNode();
tempNode.addSubNode(c,node);
}
tempNode = node;
if(i == text.length() - 1){
//关键词结束,设置结束标志
tempNode.setKeyWordEnd(true);
}
}
}
//过滤敏感词
public String filter(String text){
if(StringUtils.isBlank(text)){
return text;
}
StringBuilder result = new StringBuilder();
String replaceWord = "***"; //替代的字符
TrieNode tempNode = rootNode;
int begin = 0; //开始位置
int position = 0; //当前比较的位置
while(position < text.length()){
Character c = text.charAt(position);
//空格直接跳过
if(isSymbol(c)){
if(tempNode == rootNode){
result.append(c);
++begin;
}
++position;
continue;
}
tempNode = tempNode.getSubNode(c);
//当前位置的匹配结束
if(tempNode == null){
// 以begin开始的字符串不存在敏感词
result.append(text.charAt(begin));
// 跳到下一个字符开始测试
position = begin + 1;
begin = position;
//回到树的初始节点
tempNode = rootNode;
}else if(tempNode.isKeyWordEnd()){
// 发现敏感词, 从begin到position的位置用replacement替换掉
result.append(replaceWord);
position = position + 1;
begin = position;
tempNode = rootNode;
}else{
++position;
}
}
result.append(text.substring(begin));
return result.toString();
}
public static void main(String[] args){
SensitiveService sensitiveService = new SensitiveService();
sensitiveService.addWord("色情");
sensitiveService.addWord("赌博");
String str = "你好 ▄色▄▄情▄";
System.out.println(sensitiveService.filter(str));
}
}
“`