java 根据字典过滤词汇_利用利用字典树(前缀树)过滤敏感词

本文介绍了如何使用Java实现字典树(Trie树)过滤敏感词。通过构建字典树,能有效减少敏感词过滤过程中的字符串比较,提高算法效率。文章详细阐述了字典树的特性、构建过程以及过滤算法,并提供了源码实现。
摘要由CSDN通过智能技术生成

字典树介绍

52709faef79c

Paste_Image.png

又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

特性

根节点不包含字符,除根节点外每一个节点都只包含一个字符

从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串

每个节点的所有子节点包含的字符都不相同。

显然面对大量文本和大量敏感词,利用字典树过滤敏感词是明智而有效的,可以大量的减少重复的抖动,从而降低时间复杂度。

算法描述

通过读入的一个个敏感词来构造我们自己的字典树,可以将敏感词放到配置文件中...

首先创建字典树的结点。

我们可以定义一个boolean成员变量isEnd来标识当前结点是否是敏感词的结尾字,即该节点连上其上面的结点可以构成一个敏感词。

定义一个Map成员变量来存储当前结点的所有子节点(一层)(根节点不包含字符)

对外提供一些方法如:添加、获得结点。以方便构造字典树

结点的类定义代码如下:

private class TrieNode{

/**

* 标识当前结点是否是一个“关键词”的最后一个结点

* true 关键词的终结 false 继续

*/

private boolean isEnd = false;

/**

* 用map来存储当前结点的所有子节点,非常的方便

* key 下一个字符 value 对应的结点

*/

private Map subNodes = new HashMap<>();

/**

* 向指定位置添加结点树

* @param key

* @param node

*/

public void addSubNode(Character key , TrieNode node){

subNodes.put(key , node);

}

/**

* 根据key获得相应的子节点

* @param key

* @return

*/

public TrieNode getSubNode(Character key){

return subNodes.get(key);

}

//判断是否是关键字的结尾

public boolean isKeyWordEnd(){

return isEnd;

}

//设置为关键字的结尾

public void setKeyWordEnd(boolean isEnd){

this.isEnd = isEnd;

}

}

2.构造字典树

/**

* 核心算法一:构建字典树

* 根据输入的字符串,逐步构建字典树

* @param textLine

*/

private void addDirTreeNode(String textLine){

//边界处理

if(textLine == null)

return;

//临时结点指向根结点

TrieNode tempNode = root;

for(int i = 0; i < textLine.length(); i++){

char charWord = textLine.charAt(i);

//直接跳过非法文字

if (isSymbol(charWord))

continue;

TrieNode node = tempNode.getSubNode(charWord);

if (node == null){

//说明tempNode第一次碰到该关键字结点

node = new TrieNode();

tempNode.addSubNode(charWord , node);

}

//tempNode指向下一个结点,开始下一次循环

tempNode = node;

//到敏感词的最后一个字时,标记为红色(关键词结尾)

if (i == textLine.length() - 1)

tempNode.setKeyWordEnd(true);

}

}

3.过滤算法

定义三个指针

tempNode : 指向字典树的根节点。

position :当前比较的位置,开始下标0

begin : begin总是不断向前,position匹配失败的时候,需要回滚。开始下标为0.

position所在位置的字符,字典树的根节点的所有子节点中没有该字符,则说明该字符不可能构成敏感词,因此begin、position均可前进一位,同时tempNode回溯到根节点。

if (tempNode == null){

//以begin开始的字符串不存在敏感词

results.append(text.charAt(begin));

position = begin + 1;

begin = position;

tempNode = root;

}

position向前不断移动,并且和字典树中的敏感词一一对应,最终到tempNode指向isEnd为true的结点时,匹配成功,需要替换敏感词,并且position需要前进一位,begin移动到和position相同的位置。

else if (tempNode.isKeyWordEnd()){

results.append(DEFAULT_REPLACE_SENSITIVE);

position = position + 1;

begin = position;

tempNode = root;

}

过滤算法详细代码

/**

* 核心算法二:

* 过滤文本中的敏感词汇

* @param text

* @return

*/

public String filterWords(String text){

if (StringUtils.isBlank(text))

return text;

StringBuilder results = new StringBuilder();

TrieNode tempNode = root;

int begin = 0;//回滚数

int position = 0;//当前比较的位置

while (position < text.length()){

char word = text.charAt(position);

if (isSymbol(word)){

if (tempNode == root){

results.append(word);

++begin;

}

++position;

continue;

}

tempNode = tempNode.getSubNode(word);

if (tempNode == null){

//以begin开始的字符串不存在敏感词

results.append(text.charAt(begin));

position = begin + 1;

begin = position;

tempNode = root;

}else if (tempNode.isKeyWordEnd()){

results.append(DEFAULT_REPLACE_SENSITIVE);

position = position + 1;

begin = position;

tempNode = root;

}else {

++position;

}

}

results.append(text.substring(begin));

return results.toString();

}

算法实现

优化点

过滤非法字符(颜文字、空格...)即不可能组成敏感词的字符,提高算法准确性、性能...。

/**

* 判断是否是非法字符(即不可能存在敏感词汇的字)

* @param character

* @return true:非法字符

*/

private boolean isSymbol(Character character){

int ic = (int)character;

//东亚文字 0x2e80 —— 0x9fff

return !CharUtils.isAsciiAlphanumeric(character) && (ic < 0x2e80 || ic > 0x9fff);

}

全部源码

import java.util.HashMap;

import java.util.Map;

public class Test {

//默认敏感词替换符

private static final String DEFAULT_REPLACEMENT = "敏感词";

//根节点

private TrieNode rootNode = new TrieNode();

private class TrieNode {

/**

* true 关键词的终结 ; false 继续

*/

private boolean end = false;

/**

* key下一个字符,value是对应的节点

*/

private Map subNodes = new HashMap();

/**

* 向指定位置添加节点树

*/

void addSubNode(Character key, TrieNode node) {

subNodes.put(key, node);

}

/**

* 获取下个节点

*/

TrieNode getSubNode(Character key) {

return subNodes.get(key);

}

boolean isKeywordEnd() {

return end;

}

void setKeywordEnd(boolean end) {

this.end = end;

}

}

//判断是否是一个符号

private boolean isSymbol(char c) {

int ic = (int) c;

// 0x2E80-0x9FFF 东亚文字范围

return !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')|| (c >= 'A' && c <= 'Z')) && (ic < 0x2E80 || ic > 0x9FFF);

}

/**

* 过滤敏感词

*/

public String filter(String text) {

if (text.trim().length() == 0) {

return text;

}

String replacement = DEFAULT_REPLACEMENT;

StringBuilder result = new StringBuilder();

TrieNode tempNode = rootNode;

int begin = 0; // 回滚数

int position = 0; // 当前比较的位置

while (position < text.length()) {

char c = text.charAt(position);

// 空格直接跳过

if (isSymbol(c)) {

if (tempNode == rootNode) {

result.append(c);

++begin;

}

++position;

continue;

}

tempNode = tempNode.getSubNode(c);

// 当前位置的匹配结束

if (tempNode == null) {

// 以begin开始的字符串不存在敏感词

result.append(text.charAt(begin));

// 跳到下一个字符开始测试

position = begin + 1;

begin = position;

// 回到树初始节点

tempNode = rootNode;

} else if (tempNode.isKeywordEnd()) {

// 发现敏感词, 从begin到position的位置用replacement替换掉

result.append(replacement);

position = position + 1;

begin = position;

tempNode = rootNode;

} else {

++position;

}

}

result.append(text.substring(begin));

return result.toString();

}

/**

* 构造字典树

* @param lineTxt

*/

private void addWord(String lineTxt) {

TrieNode tempNode = rootNode;

// 循环每个字节

for (int i = 0; i < lineTxt.length(); ++i) {

Character c = lineTxt.charAt(i);

// 过滤空格

if (isSymbol(c)) {

continue;

}

TrieNode node = tempNode.getSubNode(c);

if (node == null) { // 没初始化

node = new TrieNode();

tempNode.addSubNode(c, node);

}

tempNode = node;

if (i == lineTxt.length() - 1) {

// 关键词结束, 设置结束标志

tempNode.setKeywordEnd(true);

}

}

}

public static void main(String[] argv) {

Test s = new Test();

s.addWord("sb");

s.addWord("zz");

System.out.print(s.filter("klsfjkzzlsO(∩_∩)Odjflksbj"));

}

}

运行结果

klsfjk敏感词lsO(∩_∩)Odjflk敏感词j

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值