敏感词过滤

  实现 敏感词过滤 我们用的是DFA思想,就是提前构建好一个Trie树(前缀树),让指定词在前缀树中搜索,搜索过程类似于KMP算法,找到了就是敏感词,否则就不是。那么Trie树是什么呢?
  Trie树 是一个数据结构,一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。
在这里插入图片描述
  例如:我们将ABD、ABE、AC三个字符串按Trie树存储(上图)。由于ABD和ABE有相同的前缀AB,因此我们将D、E分别放在B节点下,这样就避免了相同前缀的重复查找,从而提高查找效率。
  Trie树可以最大限度地减少无谓的字符串比较,可以用于词频统计和大量字符串排序,其最坏情况时间复杂度比hash表好。但它是一个以 空间换时间 的算法,在查找效率高的前提下也要消耗一定的内存。
  DFA(Deterministic Finite Automaton):确定有穷自动机。表现为:有一个有限状态集合和一些从一个状态通向另一个状态的边,每条边上标记有一个符号,其中一个状态是初态,某些状态是终态。由Trie树为基础可以构造出Trie图。

代码实现:
  过滤敏感词首先需要将库里的敏感词构建成Trie图,再根据指定词的字符逐个获取,直至结束。

  1. 首先定义一个用于存储每个敏感词字符结点的数据结构,通过该节点可以指向该敏感词的下一个敏感字符,每个节点需要一个 isEnd 字段标识,用于识别该字符是否是敏感词的最后一个字符。
import java.util.HashMap;
import java.util.Map;

public class WordTree {
	private Map<Character, WordTree> treeMap;
	private boolean isEnd;
	
	public WordTree() {
		this.treeMap = new HashMap<>();
	}
	
	public void addChar(char c) {
		this.treeMap.put(c, new WordTree());
	}
	
	public WordTree get(char c) {
		return treeMap.get(c);
	}
	
	public boolean containsChar(char c) {
		return this.treeMap.containsKey(c);
	}
	
	public void setEnd() {
		this.isEnd = true;
	}
	
	public boolean isEnd() {
		return isEnd;
	}

	@Override
	public String toString() {
		return "WordTree [treeMap=" + treeMap + ", isEnd=" + isEnd + "]";
	}
	
}
  1. 初始化 敏感词树
	/**
	 * 敏感词树
	 */
	private static WordTree sensitiveWords;
	
	static {
		sensitiveWords = new WordTree();
		constructTree("src/sensitive1.txt");
	}

	private static void constructTree(String path) {
		Set<String> words;
		try {
			words = readSensitiveFile(path);
		} catch (Exception e) {
			log.error("e -> ", e);
			return;
		}
		
		// 构建前缀树
		WordTree tree = sensitiveWords;
		for (String elem : words) {
			WordTree tTree = tree;
			for (int i = 0; i < elem.length(); i++) {
				char c = elem.charAt(i);
				if (!tTree.containsChar(c)) {
					tTree.addChar(c);
				}
				tTree = tTree.get(c);
				
				if (i == elem.length() - 1) {
					tTree.setEnd();
				}
			}
		}
		log.info("敏感词树构建结束");
	}

	private static Set<String> readSensitiveFile(String path) throws IOException {
		File file = new File(path);
		if (!file.exists()) {
			log.error("敏感词文件不存在");
			throw new FileNotFoundException();
		}
		
		// 读取文件中敏感词
		Set<String> words = new HashSet<>();
		BufferedReader reader = new BufferedReader(new FileReader(file));
		String line;
		while (null != (line = reader.readLine()) && line.trim().length() > 0) {
			words.add(line.trim());
		}
		reader.close();
		log.info("敏感词文件读取结束");
		
		return words;
	}
  1. 实现敏感词查找
	/**
	 * 无意义字符正则
	 */
	private static final String meaninglessRegex = "[^(a-zA-Z0-9\\u4e00-\\u9fa5)+]";

	/**
	 * 检查敏感词
	 */
	public static Set<String> checkSensitive(String sentence) {
		final Set<String> output = new HashSet<>();
		
		if (sentence != null && sentence.length() > 0) {
			// 去除无意义的字符
			sentence = sentence.replaceAll(meaninglessRegex, "");
			
			int start = 0;
			boolean lastCharIsSensitive = false; // 标识上一个次是否为敏感词
			WordTree initTree = sensitiveWords;
			WordTree tree = initTree;
			
			for (int i = 0; i < sentence.length(); i++) {
				char c = sentence.charAt(i);
				
				// 本次的词与上一敏感词无关,则重头开始
				if (lastCharIsSensitive && !tree.containsChar(c)) {
					tree = initTree.get(c);
					start = i;
				} else {					
					tree = tree.get(c);
				}
				
				if (tree == null) {
					start = i + 1;
					tree = initTree;
					lastCharIsSensitive = false;
				} else {
					lastCharIsSensitive = true;
					if (tree.isEnd()) {
						output.add(sentence.substring(start, i+1));
						start = i + 1;
						tree = initTree;
					}
				}
			}
			
		}		
		
		return output;
	}

完整源码下载地址

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 设计师:骄傲的白兰地 返回首页