过滤敏感词工具类SensitiveFilter
- 网上过滤敏感词工具类有的存在挺多bug,这是我自己改用的过滤敏感词工具类,目前来说没啥bug,如果有bug欢迎在评论指出
 - 使用前缀树 
Trie实现的过滤敏感词,树节点用静态内部类表示了,都写在一个SensitiveFilter一个文件里了 
package top.linzeliang.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;
/**
 * 敏感词过滤
 *
 * @Author: linzeliang
 * @Date: 2021/12/8
 */
@Component
public class SensitiveFilter {
    private static final Logger LOGGER = LoggerFactory.getLogger(SensitiveFilter.class);
    /**
     * 替换符
     */
    private static final String REPLACEMENT = "*";
    /**
     * 根节点,根节点是不带值的
     */
    private final TrieNode ROOT_NODE = new TrieNode();
    /**
     * 初始化前缀树,读取敏感词文件构造前缀树
     *
     * @date 2021/12/9
     */
    @PostConstruct
    private void init() {
        try (
                InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
        ) {
            String keyword;
            // 每次读取一行
            while ((keyword = reader.readLine()) != null) {
                // 添加到前缀树
                this.addKeyword(keyword);
            }
        } catch (IOException e) {
            LOGGER.error("加载敏感词文件失败: " + e.getMessage());
        }
    }
    /**
     * 将一个敏感词添加到前缀树中
     *
     * @param keyword 敏感词
     * @date 2021/12/9
     */
    private void addKeyword(String keyword) {
        TrieNode tempNode = ROOT_NODE;
        for (int i = 0; i < keyword.length(); i++) {
            //获取单个字符
            char c = keyword.charAt(i);
            // 先查询是否存在,就是是否有这个开头的敏感词
            TrieNode subNode = tempNode.getSubNode(c);
            // 如果子节点中不存在,就新建,并且添加到tempNode的子节点
            if (null == subNode) {
                subNode = new TrieNode();
                tempNode.addSubNodes(c, subNode);
            }
            // 标记一下最后一个节点,即叶子节点
            if (i == keyword.length() - 1) {
                subNode.setKeywordEnd(true);
            }
            // 将指针指向子节点
            tempNode = subNode;
        }
    }
    /**
     * 过滤敏感词
     *
     * @param text 待过滤文本
     * @return java.lang.String
     * @date 2021/12/9
     */
    public String filter(String text) {
        // 过滤文本为空返回 null
        if (StringUtils.isBlank(text)) {
            return null;
        }
        // 指针1,刚开始指向根节点
        TrieNode tempNode = ROOT_NODE;
        // 指针2
        int start = 0;
        // 指针3
        int end = 0;
        // 过滤结果
        StringBuilder sb = new StringBuilder();
        // 当指针3未到字符串末尾时,都进行过滤
        while (end < text.length()) {
            // 获取待过滤的每个字符
            char c = text.charAt(end);
            // 如果是无效符号就跳过
            if (isSymbol(c) && end != text.length() - 1) {
                // 若指针1处于根节点,就将此符号计入结果,让指针2向下走一步
                if (tempNode == ROOT_NODE) {
                    sb.append(c);
                    start++;
                }
                // 无论符号在开头或中间,指针3都向下走一步
                end++;
                continue;
            }
            // 查看敏感字符对应的子节点是否存在
            tempNode = tempNode.getSubNode(c);
            // 如果没有敏感词对应的子节点,说明不包含,因此跳过这个字符
            if (tempNode == null) {
                // 以begin开头的字符串不是敏感词
                sb.append(text.charAt(start));
                // start 和 begin 都进入下一个位置
                end = ++start;
                // 重新指向根节点
                tempNode = ROOT_NODE;
            } else if (tempNode.isKeywordEnd()) {
                // 遇到敏感词结束标识,即发现敏感词,将begin~position字符串替换掉
                for (int i = start; i <= end; i++) {
                    sb.append(REPLACEMENT);
                }
                // 进入下一个位置
                start = ++end;
                // 重新指向根节点
                tempNode = ROOT_NODE;
            } else {
                // 如果找到了敏感字符,但是又没结束,因此继续检查下一个字符
                // 如果当前 start 字符到 end 末尾字符没有识别出敏感词,那么就从 start 的下一个开始进行查找
                if (end < text.length() - 1) {
                    end++;
                } else {
                    // 这里还是指向 start,并没有加 1,因为下一步循环就进入到 tempNode == null 判断里面了
                    // 因此 start 和 end 都会加 1,同时上一个字符也会被加入到sb中
                    end = start;
                }
            }
        }
        // 将最后一批字符计入结果
        sb.append(text.substring(start));
        return sb.toString();
    }
    /**
     * 判断是否为符号
     *
     * @param c 待判断符号
     * @return boolean
     * @date 2021/12/9
     */
    private boolean isSymbol(Character c) {
        // 0x2E80~0x9FFF 是东亚文字范围
        return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
    }
    /**
     * 前缀树节点
     * 因为不需要用到外部类SensitiveFilter,所以设置成静态的就行,能提高性能
     */
    private static class TrieNode {
        /**
         * 关键词结束标识符
         */
        private boolean isKeywordEnd;
        /**
         * 存放子节点
         * 因为子节点集合是固定的,只会往这个集合增删元素,而不会改变这个集合指针指向,所以使用final
         */
        private final Map<Character, TrieNode> subNodes;
        public TrieNode() {
            this.isKeywordEnd = false;
            this.subNodes = new HashMap<>();
        }
        public boolean isKeywordEnd() {
            return isKeywordEnd;
        }
        public void setKeywordEnd(boolean keywordEnd) {
            isKeywordEnd = keywordEnd;
        }
        /**
         * 添加子节点
         *
         * @param c    节点名称
         * @param node 节点
         * @date 2021/12/9
         */
        public void addSubNodes(Character c, TrieNode node) {
            subNodes.put(c, node);
        }
        /**
         * 获取子节点
         *
         * @param c 查询的字符
         * @return top.linzeliang.community.util.SensitiveFilter.TrieNode
         * @date 2021/12/9
         */
        public TrieNode getSubNode(Character c) {
            return subNodes.get(c);
        }
    }
}
												
											过滤敏感词工具类SensitiveFilter的更多相关文章
- Java 敏感词过滤,Java 敏感词替换,Java 敏感词工具类
		
Java 敏感词过滤,Java 敏感词替换,Java 敏感词工具类 =========================== ©Copyright 蕃薯耀 2017年9月25日 http://www ...
 - 【SpringBoot】前缀树 Trie 过滤敏感词
		
1.过滤敏感词 Spring Boot实践,开发社区核心功能 完成过滤敏感词 Trie 名称:Trie也叫做字典树.前缀树(Prefix Tree).单词查找树 特点:查找效率高,消耗内存大 应用:字 ...
 - SpringBoot开发十四-过滤敏感词
		
项目需求-过滤敏感词 利用 Tire 树实现过滤敏感词 定义前缀树,根据敏感词初始化前缀树,编写过滤敏感词的方法 代码实现 我们首先把敏感词存到一个文件 sensitive.txt: 赌博 嫖娼 吸毒 ...
 - web前端js过滤敏感词
		
web前端js过滤敏感词 这里是用文本输入框还有文本域绑定了失去焦点事件,然后再遍历敏感词数组进行匹配和替换. var keywords=["阿扁","呵呵", ...
 - (转)两种高效过滤敏感词算法--DFA算法和AC自动机算法
		
原文:https://blog.csdn.net/u013421629/article/details/83178970 一道bat面试题:快速替换10亿条标题中的5万个敏感词,有哪些解决思路? 有十 ...
 - PHP 扩展 trie-tree, swoole过滤敏感词方案
		
在一些app,web中评论以及一些文章会看到一些*等,除了特定的不显示外,我们会把用户输入的一些敏感字符做处理,具体显示为*还是其他字符按照业务区实现. 下面简单介绍下业务处理. 原文地址:小时刻个人 ...
 - php过滤敏感词
		
<?php /** * 敏感词过滤工具类 * 使用方法 * echo FilterTools::filterContent("你妈的我操一色狼杂种二山食物"," ...
 - [转]Filter实现处理中文乱码,转义html标签,过滤敏感词
		
原文地址:http://www.cnblogs.com/xdp-gacl/p/3952405.html 在filter中可以得到代表用户请求和响应的request.response对象,因此在编程中可 ...
 - js 过滤敏感词 ,可将带有标点符号的敏感词过滤掉
		
function transSensitive(content) { // var Sensitive = H.getStorage("Sensitive");//敏感词数组 va ...
 
随机推荐
- Spring 之 BeanFactory 源码 - 接口分析
			
一.BeanFactory的基本类体系结构(接口为主):
 - JS中如何将yyyy-MM-dd HH:mm:ss格式的字符串转成Date类型
			
var deadline = '2019-04-11 13:11:00'; var result = new Date(deadline.replace(/-/g, '/'));
 - mysql 数据库中 int(3) 和 int(11) 有区别么???
			
今天去面试的时候 面试官问到了这个问题:int(3) 和 int(11) 有什么区别?? 当时一听有点蒙,(不知道为什么蒙,后来回来想想可能是觉得考官怎么会问这么简单的问题呢,所以蒙了),当时我的回答 ...
 - [luogu5344]逛森林
			
由于没有删边操作,可以先建出整棵森林,之后再用并查集判断是否连通,若连通必然与最后的森林相同 但如果用树链剖分+线段树的形式来优化建图,更具体如下: 建立两颗线段树,左边从儿子连向父亲,右边从父亲连向 ...
 - [atARC101F]Robots and Exits
			
每一个点一定匹配其左边/右边的第一个出口(在最左/右边的出口左/右边的点直接删除即可),否则记到左右出口的距离分别为$x_{i}$和$y_{i}$ 令$p_{i}$表示$i$匹配的出口(左0右1),结 ...
 - 手把手教你实现Android真机远程截屏
			
先看效果演示 接下来手把手教你实现这样的效果. minicap简介 minicap是一个可以远程获取android屏幕画面的开源库,它在低版本的Android系统上采用截屏的方式获取画面,在Andro ...
 - python 配置pip镜像源
			
在本地用户下新建pip文件夹,新建pip.ini [global] index-url = http://mirrors.aliyun.com/pypi/simple/ [install] trust ...
 - idea插件  Background Image Plus 随机更换背景图片
			
首先在市场搜索: Background Image Plus 设置图片: 在view中,有set 图片,有random图片,有clean图片的 设置就是用set,随便设置个路径. 重点来了,随机更换背 ...
 - 状压DP详解+题目
			
介绍 状压dp其实就是将状态压缩成2进制来保存 其特征就是看起来有点像搜索,每个格子的状态只有1或0 ,是另一类非常典型的动态规划 举个例子:有一个大小为n*n的农田,我们可以在任意处种田,现在来描述 ...
 - 从记账软件看工具类APP的存量运营之道
			
随着移动互联网的发展,APP的种类越来越多,一些工具类 APP 增长乏力,难以实现长期增长.只有提高用户留存时间,实现流量变现,才能在激烈的市场竞争中持续发展. 工具类APP的特点: 替代性很强: 用 ...