实现了一个基于前缀树(Trie 树)的敏感词过滤工具类SimpleTrie
,用于高效地检测文本中是否包含敏感词。下面我将对这个类进行解析,并说明其使用方法和特点。
核心功能解析
前缀树结构:
- 使用
Map<Character, Object>
作为节点,每个节点存储字符到子节点的映射 - 特殊字符
CHARACTER_END = '\0'
标记一个敏感词的结束
- 使用
构造方法:
- 接收敏感词集合,构建前缀树
- 对敏感词进行排序,优先处理较短的敏感词
- 构建树时,如果遇到已标记结束的节点则停止,实现最短匹配原则
主要方法:
isValid(String text)
:检查文本是否合法(不包含任何敏感词)validate(String text)
:获取文本中包含的所有敏感词(去重)
递归辅助方法:
recursion()
:用于验证文本是否包含敏感词recursionWithResult()
:在验证的同时记录匹配到的敏感词
import cn.hutool.core.collection.CollUtil;
import java.util.*;
/**
* 基于前缀树,实现敏感词的校验
* <p>
* 相比 Apache Common 提供的 PatriciaTrie 来说,性能可能会更加好一些。
*
*/
@SuppressWarnings("unchecked")
public class SimpleTrie {
/**
* 一个敏感词结束后对应的 key
*/
private static final Character CHARACTER_END = '\0';
/**
* 使用敏感词,构建的前缀树
*/
private final Map<Character, Object> children;
/**
* 基于字符串,构建前缀树
*
* @param strs 字符串数组
*/
public SimpleTrie(Collection<String> strs) {
// 排序,优先使用较短的前缀
strs = CollUtil.sort(strs, String::compareTo);
// 构建树
children = new HashMap<>();
for (String str : strs) {
Map<Character, Object> child = children;
// 遍历每个字符
for (Character c : str.toCharArray()) {
// 如果已经到达结束,就没必要在添加更长的敏感词。
// 例如说,有两个敏感词是:吃饭啊、吃饭。输入一句话是 “我要吃饭啊”,则只要匹配到 “吃饭” 这个敏感词即可。
if (child.containsKey(CHARACTER_END)) {
break;
}
if (!child.containsKey(c)) {
child.put(c, new HashMap<>());
}
child = (Map<Character, Object>) child.get(c);
}
// 结束
child.put(CHARACTER_END, null);
}
}
/**
* 验证文本是否合法,即不包含敏感词
*
* @param text 文本
* @return 是否 true-合法 false-不合法
*/
public boolean isValid(String text) {
// 遍历 text,使用每一个 [i, n) 段的字符串,使用 children 前缀树匹配,是否包含敏感词
for (int i = 0; i < text.length(); i++) {
Map<Character, Object> child = (Map<Character, Object>) children.get(text.charAt(i));
if (child == null) {
continue;
}
boolean ok = recursion(text, i + 1, child);
if (!ok) {
return false;
}
}
return true;
}
/**
* 验证文本从指定位置开始,是否不包含某个敏感词
*
* @param text 文本