SpringBoot使用前缀树过滤敏感词
前缀树
Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较。
Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
- 名称:Trie、字典树、查找树
- 特点:查找效率高,消耗内存大
- 应用:字符串检索、词频统计、字符串排序
前缀树的3个基本性质:
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符都不相同。
敏感词过滤器
- 定义前缀树
- 根据敏感词,初始化前缀树
- 编写过滤敏感词的方法
1. 首先建立敏感词词汇文件,这里选择在 resources 目录下建立 sensitive-words.txt,文件中每一行存放一个敏感词,你可以在里面添加任意个敏感词。
2. 创建敏感词过滤器工具类 SensitiveFilter.java,在里面定义一个 TrieNode 内部类为前缀树节点的数据结构。
package com.wxy.community.util;import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;@Component
public class SensitiveFilter {// 前缀树private class TrieNode{// 子节点(key是下级字符, value是下级节点)private Map<Character, TrieNode> subNodes = new HashMap<>();// 关键词结束标识private boolean isKeywordEnd = false;// 添加子节点public void addSubNode(Character c, TrieNode node){subNodes.put(c, node);}// 获取子节点public TrieNode getSubNode(Character c){return subNodes.get(c);}public boolean isKeywordEnd() {return isKeywordEnd;}public void setKeywordEnd(boolean keywordEnd) {isKeywordEnd = keywordEnd;}}
}
3. 根据敏感词来初始化前缀树, 设置 init() 方法初始化前缀树,并且使用 @PostConstruct 注解当服务启动的时候就初始化前缀树。
package com.wxy.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;@Component
public 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));){String keyword;while ((keyword = reader.readLine()) != null){// 添加到前缀树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);if (subNode == null){// 初始化子节点subNode = new TrieNode();tempNode.addSubNode(c, subNode);}// 指向子节点,进入下一轮循环tempNode = subNode;// 设置结束标识if (i == keyword.length() - 1){tempNode.setKeywordEnd(true);}}}// 省略 前缀树内部类
}
4. 最后编写过滤敏感词的方法, 这样就完成了敏感词过滤器类的编写。
package com.wxy.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;@Component
public class SensitiveFilter {// 省略 init初始化方法/*** 过滤敏感词* @param text 待过滤的文本* @return 过滤后的文本*/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){// 0x2E80 ~ 0x9FFF 是东亚文字范围return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);}// 省略 内部类前缀树
}
SensitiveFilter.java 完整代码:
package com.wxy.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;@Component
public 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));){String keyword;while ((keyword = reader.readLine()) != null){// 添加到前缀树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);if (subNode == null){// 初始化子节点subNode = new TrieNode();tempNode.addSubNode(c, subNode);}// 指向子节点,进入下一轮循环tempNode = subNode;// 设置结束标识if (i == keyword.length() - 1){tempNode.setKeywordEnd(true);}}}/*** 过滤敏感词* @param text 待过滤的文本* @return 过滤后的文本*/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){// 0x2E80 ~ 0x9FFF 是东亚文字范围return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);}// 前缀树private class TrieNode{// 子节点(key是下级字符, value是下级节点)private Map<Character, TrieNode> subNodes = new HashMap<>();// 关键词结束标识private boolean isKeywordEnd = false;// 添加子节点public void addSubNode(Character c, TrieNode node){subNodes.put(c, node);}// 获取子节点public TrieNode getSubNode(Character c){return subNodes.get(c);}public boolean isKeywordEnd() {return isKeywordEnd;}public void setKeywordEnd(boolean keywordEnd) {isKeywordEnd = keywordEnd;}}
}
编写测试类查看是否能够成功过滤掉敏感词,可以看到成功过滤掉了敏感词汇。
测试类代码:
package com.wxy.community;import com.wxy.community.util.SensitiveFilter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class SensitiveTests {@Autowiredprivate SensitiveFilter sensitiveFilter;@Testpublic void testSensitiveFilter(){String text = "这里可以赌博,可以嫖娼,可以吸毒,可以开票";text = sensitiveFilter.filter(text);System.out.println(text);String text2 = "这里可以❤赌❤博❤,可以❤嫖❤娼❤,可以❤吸❤毒❤,可以❤开❤票❤";text2 = sensitiveFilter.filter(text2);System.out.println(text2);}
}
详细视频:牛客网论坛项目-过滤敏感词
前缀树介绍
SpringBoot使用前缀树过滤敏感词相关推荐
- SpringBoot使用前缀树实现敏感词的过滤
记录一下使用SpringBoot中使用前缀树对敏感词的一个过滤. 首先呢在resources目录下建立一个文件用来装敏感词例如我在resources/sensitive-words.txt如下: 敏感 ...
- java实现前缀树--过滤敏感词汇
一.前缀树简单介绍 Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.它 ...
- 前缀树实现敏感词过滤
前缀树的节点类 为了方便功能的实现,我们需要有一个Boolean类型的变量来标记该节点是否为叶子节点, 同样的为了方便判断是否存在某个字符在前缀树中,可以把子节点作成Map的形式,Map的key为该字 ...
- 通过前缀树的敏感词过滤算法
敏感词过滤器,配置文件分行写 如: ab cd ef import lombok.Data; import org.apache.commons.lang3.CharUtils; import org ...
- trie树之敏感词过滤算法
之前写过一篇关于Trie树的介绍:Trie树--在一个字符串集合中快速查找某个字符串.今天就用Trie树来实现敏感词过滤算法. 首先简单介绍一下Trie树的数据结构: 1.根节点不存储字符. 2.Tr ...
- 过滤敏感词汇(trie树实现敏感词过滤)
1.字符串匹配 (1)暴力解法:采用两个for循环,每次匹配的时间复杂度O(m*n) (2)KMP:时间复杂度O(m+n) n表示字符串的长度,m表示每个灵感词的长度 (3)trie树 字典树/单词查 ...
- dfa算法c语言,DFA跟trie字典树实现敏感词过滤(python和c语言)
DFA和trie字典树实现敏感词过滤(python和c语言) 现在做的项目都是用python开发,需要用做关键词检查,过滤关键词,之前用c语言做过这样的事情,用字典树,蛮高效的,内存小,检查快. 到了 ...
- 使用数据结构过滤敏感词算法
前缀树 名称:Tire.字典树.查找树 特点:查找效率高,消耗内存大 应用:字符串检索.词频统计.字符串排序等 敏感词过滤器 定义前缀树 package com.nowcoder.community. ...
- python过滤敏感词记录
简述: 关于敏感词过滤可以看成是一种文本反垃圾算法,例如 题目:敏感词文本文件 filtered_words.txt,当用户输入敏感词语,则用 星号 * 替换,例如当用户输入「北京是个好城市」,则变成 ...
最新文章
- 中文的csv文件python读取编码问题
- P1912 [NOI2009]诗人小G
- 前端构建工具之争——Webpack vs Gulp 谁会被拍死在沙滩上
- Activity详细解释(生命周期、以各种方式启动Activity、状态保存,等完全退出)...
- sql表合并,统计计算,生成总计
- spring-core
- ThinkPad X220i 刷白名单BIOS,改装第三方无线网卡
- OpenStack nova-network 支持多vlan技术实现片段代码
- MySQL从5.5升级到5.6,TIMESTAMP的变化
- Myeclipse 操作数据库
- 实验楼第三次实验报告
- Google浏览器一开启就提示“请停用以开发者模式运行的扩展程序“解决方案
- 404 单页应用 报错 路由_详解vue 单页应用(spa)前端路由实现原理
- 6.跑步者--并行编程框架 ForkJoin
- 如何做项目总结与汇报
- java运行环境简称_java程序的运行环境简称为什么?
- java mina框架_Mina框架在项目中的使用(一)
- [生存志] 第14节 历代大事件概览 西汉
- App变现之Admob原生广告
- 快速幂都能做什么?小小的算法也有大大的梦想