3.1过滤敏感词( 二 )


package com.nowcoder.community.util;import org.apache.commons.lang3.CharUtils;import org.apache.commons.lang3.StringUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.util.HashMap;import java.util.Map;@Componentpublic class SensitiveFilter {private static final Logger logger = LoggerFactory.getLogger(SensitiveFilter.class);// 替换符private static final String REPLACEMENT = "***";// 根节点private TrieNode rootNode = new TrieNode();@PostConstructpublic void init() {try (InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");//字节流BufferedReader reader = new BufferedReader(new InputStreamReader(is));//把字节流变为字符流new InputStreamReader(is),然后把字符流变为缓冲流new BufferedReader(new InputStreamReader(is))) {String keyword;while ((keyword = reader.readLine()) != null) {//每次读取到的值,都存到keyword变量中// 读到敏感词 。并将其添加到前缀树this.addKeyword(keyword);}} catch (IOException 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);//tempNode当前节点 。getSubNode将字符挂在到子节点if (subNode == null) {//但如果子节点为空// 初始化子节点subNode = new TrieNode();tempNode.addSubNode(c, subNode);//将初始化好的子节点 挂到当前节点之下,c是当前字符}// 指向子节点,进入下一轮循环tempNode = subNode;// 设置结束标识 。一个单词的最后一个字符,需要设置结束标识if (i == keyword.length() - 1) {tempNode.setKeywordEnd(true);}}}/*** 过滤敏感词** @param text 待过滤的文本* @return 过滤后的文本*/public String filter(String text) {//过滤后的文本String;传入待过滤的文本textif (StringUtils.isBlank(text)) {///如果text为空return null;}// 指针1 -->指向树TrieNode tempNode = rootNode;// 指针2int begin = 0;// 指针3int position = 0;// 结果StringBuilder sb = new StringBuilder();//StringBuilder是变程字符串 。在处理变化字符串过程中,比string效率高while (position < text.length()) {//用指针3判断 结束 。因其比指针2 可能优先到达结尾 。char c = text.charAt(position);//得到当前某一字符,并对其判断 。// 跳过特殊符号if (isSymbol(c)) {//判断是否为特殊符号的函数isSymbolif (tempNode == rootNode) {// 若指针1 tempNode处于根节点,将此符号计入结果,让指针2向下走一步sb.append(c);begin++;//指针2 begin向下走一步}position++; // 无论符号在开头或中间,指针3 position都向下走一步continue;}// 检查下级节点tempNode = tempNode.getSubNode(c);if (tempNode == null) {sb.append(text.charAt(begin)); // 以begin开头的字符串不是敏感词,则把begin开头的字符 记录到position = ++begin; // 进入下一个位置tempNode = rootNode;// 树形指针归为,即重新指向根节点,重新判断} else if (tempNode.isKeywordEnd()) {// 发现敏感词,将begin~position字符串替换掉sb.append(REPLACEMENT);begin = ++position; // 进入下一个位置tempNode = rootNode;// 重新指向根节点} else {// 检查下一个字符position++;}}// 将最后一批字符计入结果:3到终点,2未到终点 。sb.append(text.substring(begin));return sb.toString();}// 判断是否为符号private boolean isSymbol(Character c) {return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);// 0x2E80~0x9FFF 是东亚文字范围}// 前缀树//定义内部类-前缀树private class TrieNode {// 关键词结束标识private boolean isKeywordEnd = false;// 子节点(key是下级字符,value是下级节点)private Map