敏感词过滤算法
敏感词过滤算法,详情见注释
# 基类
public class TrieNode {
/**
* true 关键词的终结 ; false 继续
*/
private boolean end = false;
/**
* 根节点默认大小
*/
private static final int ROOT_NODE_SIZE = 64;
/**
* 子节点默认大小
*/
private static final int SUB_NODE_SIZE = 16;
public static TrieNode buildRootNode() {
return new TrieNode(ROOT_NODE_SIZE);
}
public static TrieNode buildSubNode() {
return new TrieNode(SUB_NODE_SIZE);
}
/**
* key下一个字符,value是对应的节点
*/
private final Map<Character, TrieNode> subNodes;
public TrieNode(int size) {
subNodes = new HashMap<>(size);
}
/**
* 向指定位置添加节点树
*/
void addSubNode(Character key, TrieNode node) {
subNodes.put(key, node);
}
/**
* 获取下个节点
*/
TrieNode getSubNode(Character key) {
return subNodes.get(key);
}
boolean isKeywordEnd() {
return end;
}
void setKeywordEnd() {
this.end = true;
}
}
# 核心类
public class KeyWord {
/**
* 默认敏感词替换符
*/
private static final String DEFAULT_REPLACEMENT = "**";
/**
* 根节点
*/
private final TrieNode ROOT_NODE = TrieNode.buildRootNode();
/**
* 得到根节点
* @return 根节点
*/
private TrieNode getRootNode() {
return ROOT_NODE;
}
/**
* 判断是否是一个符号
*
* @param c char
* @return true是字符,false不是字符
*/
private boolean isSymbol(char c) {
// 0x2E80-0x9FFF 东亚文字范围
return !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) && ((int) c < 0x2E80 || (int) c > 0x9FFF);
}
/**
* 过滤敏感词
*/
public String filter(String text) {
if (text.trim().length() == 0) {
return text;
}
StringBuilder result = new StringBuilder();
//
TrieNode tempNode = getRootNode();
int begin = 0;
int position = 0;
while (position < text.length()) {
char c = text.charAt(position);
// 空格直接跳过
if (isSymbol(c)) {
//如果是根节点证明是正常字符
//如果不是根节点证明是混淆字符,例如: S*B
if (tempNode == getRootNode()) {
//记录当前字符移动指针
result.append(c);
//归位
++begin;
}
//移动指针
++position;
continue;
}
//尝试从敏感字典里获取
tempNode = tempNode.getSubNode(c);
if (tempNode == null) {
result.append(text.charAt(begin));
/*
没有获取到敏感词,有两种情况:
以敏感词:'傻逼'举例
情况1:从头到尾都没有敏感词:
例如:你是个小可爱
匹配结束,begin=6,position = 6
情况2:敏感词被截断
例如:你是个傻可爱。
匹配到疑似敏感词'傻': begin=3,position = 4
匹配到非敏感词'可',于上不足以构成完全的敏感词, 需要重新记录'傻'字 => text.charAt(3) == '傻'
由于此时 position 可能进行了多次'疑似敏感词'的位移,
而 begin 进行了一次值捕获,需要移动到下一位
所以需要重置position位置为移动后的begin位置
*/
position = ++begin;
// 因为此时没有匹配到敏感字,回到树初始节点
tempNode = getRootNode();
} else if (tempNode.isKeywordEnd()) {
//匹配到一个完整敏感词,用 DEFAULT_REPLACEMENT 替代
result.append(DEFAULT_REPLACEMENT);
//此时 begin下标还是为在: 当前敏感词首位的下标-1
//所以需要重置下标为 position ,也就是当前敏感词结束下标
begin = ++position;
//匹配敏感字完成,回到树初始节点
tempNode = getRootNode();
} else {
//疑似敏感词位移
++position;
}
}
//此处截取是为了防止末尾敏感词不全,导致的信息不全问题
// 字符:你是傻
//截取到最后,position==3,begin==2,result=='你是' ,text.substring(begin) == '傻'
//由于不够组成敏感词,需要追加
result.append(text.substring(begin));
return result.toString();
}
/**
* 构造字典树
*
* @param lineTxt 字符
*/
private void addWord(String lineTxt) {
TrieNode currNode = getRootNode();
// 循环每个字节
for (int i = 0; i < lineTxt.length(); ++i) {
Character c = lineTxt.charAt(i);
// 过滤空格
if (isSymbol(c)) {
continue;
}
//尝试获取一个敏感字符
TrieNode node = currNode.getSubNode(c);
if (node == null) {
//没拿到,初始化
node = TrieNode.buildSubNode();
currNode.addSubNode(c, node);
}
currNode = node;
if (i == (lineTxt.length() - 1) ) {
// 关键词结束, 设置结束标志
currNode.setKeywordEnd();
}
}
}
}
# 测试
public static void main(String[] argv) {
KeyWord s = new KeyWord();
s.addWord("SB");
s.addWord("TM");
System.out.println(s.filter("你是TM真是个SB"));
}
# 控制台输出
你是**真是个**
上次更新: 2024/11/05, 08:29:31