敏感词过滤
网站在输入问题或者问题内容的时候为了避免有些人打广告,或者发表一些不当言论,我们需要增加敏感词过滤这个功能。
现在一般的网站的敏感词过滤都是用前缀树实现的,那么前缀树是什么呢?(自问自答- -)
字典树又称前缀树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。
优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高
下面是我画的前缀树的一个图
可以看到一个节点中需要包含一个字符和下一个节点的连接,这样的话我们就可以用一个hashmap来保存我们需要的信息,当然,既然是敏感词的前缀树,就需要添加字符、设置敏感词结尾、判断结尾、获取下一个节点的功能
一个敏感词过滤器的组成:
1、定义前缀树
2、根据敏感词,初始化前缀树
3、编写过滤敏感词的方法
1、定义前缀树
private class TrieNode {
//关键词结束标志
private boolean isKeywordEnd = false;
//子节点(key是下级字符,value是下级节点)
private Map<Character, TrieNode> subNodes = new HashMap<>();
public boolean isKeywordEnd() {
return isKeywordEnd;
}
public void setKeywordEnd(boolean keywordEnd) {
isKeywordEnd = keywordEnd;
}
//添加子节点
public void addSubNode(Character c, TrieNode node) {
subNodes.put(c, node);
}
//获取子节点
public TrieNode getSubNode(Character c) {
return subNodes.get(c);
}
}2、初始化前缀树
//被@PostConstruct修饰的方***在服务器加载Servlet的时候运行,并且只会被服务器执行一次。
//@PostConstruct在构造函数之后执行,init()方法之前执行
@PostConstruct
public void init() {
try (
InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
) {
String keyword;
while ((keyword = reader.readLine()) != null) {
//添加到前缀树
this.addKeyword(keyword);
}
} catch (Exception e) {
logger.error("加载敏感词文件失败:" + e.getMessage());
}
}
//将敏感词添加到前缀树中
private void addKeyword(String keyword) {
TrieNode tempNode = rootNode;
for (int i = 0; i < keyword.length(); i++) {
char c = keyword.charAt(i);
TrieNode subNode = tempNode.getSubNode(c);
if (subNode == null) {
//初始化子节点
subNode = new TrieNode();
tempNode.addSubNode(c, subNode);
}
//指向子节点,进入下一轮循环
tempNode = subNode;
//设置结束标识
if (i == keyword.length() - 1) {
tempNode.setKeywordEnd(true);
}
}
}关键功能:敏感词过滤
关键的部分来了:
定义三个指针,将两个指针a、p指向文本开头,另一个指针temp指向前缀树的根节点
获取p指针指向的字符,在前缀树中获取到该节点,
1)并判断其是否为空,如果为空则不包含这个字符,p后移、a也后移
2)如果不为空且不是敏感词结尾,p++,继续上面的判断
3)如果为空且是敏感词结尾,将这一段的文本替换掉,p后移,a随着p后移继续判断,直到文本结束
3、实现过滤算法
public String filter(String text) {
if (StringUtils.isBlank(text)) {
return null;
}
//指针1
TrieNode tempNode = rootNode;
//指针2
int begin = 0;
//指针3
int position = 0;
//结果
StringBuilder sb = new StringBuilder();
while (position < text.length()) {
char c = text.charAt(position);
//跳过符号
if (isSymbol(c)) {
//若指针1处于根节点,将此符号计入计入结果,让指针2向下走一步
if (tempNode == rootNode) {
sb.append(c);
begin++;
}
//无论符号在开头还是中间,指针3都向下走一步
position++;
continue;
}
//检查下级节点
tempNode = tempNode.getSubNode(c);
if (tempNode == null) {
//以begin开头的字符串不是敏感词
sb.append(text.charAt(begin));
//进入下一位置
position = ++begin;
//重新指向根节点
tempNode = rootNode;
} else if (tempNode.isKeywordEnd()) {
//发现敏感词,将begin-position字符串替换掉
sb.append(REPLACEMENT);
//进入下一位置
begin = ++position;
//重新指向根节点
tempNode = rootNode;
} else {
//检查下一字符
position++;
}
}
//最后一批字符计入结果
sb.append(text.substring(begin));
return sb.toString();
}
//判断是否为符号
private boolean isSymbol(Character c) {
return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
}

京公网安备 11010502036488号