3.1敏感词过滤

161 阅读2分钟

主要通过前缀树实现,敏感词过滤。

前缀树通过,类TreeNode,中的属性HashMap实现,hashmap中的value值又是个TreeNode。这样就形成了一个类似树的结构。往树上正确添加结点很重要

然后字符串与前缀树的单词匹配 是通过双指针实现的。具体看代码吧

1.当没有匹配到敏感词时,begin指针++,position指向begin。并且把这个字符加上res结果集。

2.当匹配到第一个敏感词时,就继续往后匹配,通过position++,往后begin暂时不动,直到匹配到最后一个叶子结点。把结果集变成***,然后position++,begin跟上position,从刚匹配到的敏感词后开始。

如果最后不是敏感词,begin++,position = begin。

第三个难点,特殊字符,比如❥。这种不能干扰敏感词的判断

1.如果特殊字符位于字符首,只要begin++就行 position = begin。然后把特殊字符加入就行。

2.特殊字符位于敏感词中间,只要position++就行。然后continue

`~M$D(87M9}8K3}VU{51@PA.png

/**
 * @Description:
 * @Author: cjh
 * @data: Version 1.0
 */
@Component
public class SensitiveFilter {

    private static final Logger logger = LoggerFactory.getLogger(SensitiveFilter.class);
    private static final String REPLACE = "***";

    //root
    private TreeNode rootNode = new TreeNode();

    /**
     *这个注解表示这是个初始化方法,当容器实例化这个bean之后,这个方法会被自动调用。在服务器启动时就会调用
     * 启动玩服务器就能对敏感词筛选了
     */
    @PostConstruct()
    public void init(){
        try (
                //获取类加载器,默认从类路径下加载资源。就是编译后的classes下
                InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
                //读文字用字符流,但是字符缓冲流更快
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resourceAsStream));

       ){
            String keyWord;
            //这个表示一次读一行
            while ((keyWord = bufferedReader.readLine()) != null){
                //添加到前缀树
                this.addKeyWord(keyWord);
            }
        }catch (IOException e){
            logger.error("加载敏感词文件失败:"+ e.getMessage());
        }
    }

    //添加一个词在前缀树上
    private void addKeyWord(String keyWord) {
        TreeNode root = rootNode;
        for (int i = 0; i < keyWord.length(); i++) {
            char c = keyWord.charAt(i);
            //先看下有没有创建
            TreeNode next = root.getNextNode(c);
            if (next == null){
                next = new TreeNode();
                root.addNextNode(c,next);
            }

            //指向子节点,进行下次循环
            root = next;
            //设置结束标志
            if (i == keyWord.length() - 1){
                root.setKeyWordEnd(true);
            }
        }
    }

    /**
     * 过滤开始
     */
    public String filter(String text){
        if (StringUtils.isBlank(text)){
            return null;
        }

        //指针1,指向树的用于遍历
        TreeNode tempNode = rootNode;
        //指针2,3用于字符串的遍历比较
        int begin = 0;
        int position = 0;
        //结果
        StringBuilder stringBuilder = new StringBuilder();

        while (begin < text.length()){
            if (position < text.length()){
                char c = text.charAt(position);
                //跳过特殊字符
                if (isSymbol(c)){
                    //若指针1处于根节点,将此符号计入结果,让指针2向下走一步
                    if (tempNode == rootNode){
                        stringBuilder.append(c);
                        begin++;
                    }
                    //无论符号在哪,前面还是中间指针3都往下走
                    position++;
                    continue;

                }

                //检查下级节点
                tempNode = tempNode.getNextNode(c);
                if(tempNode == null){
                    //没有检查到敏感词
                    stringBuilder.append(text.charAt(begin));
                    //下个位置
                    position = ++begin;
                    //重新指向root节点
                    tempNode = rootNode;
                }else if (tempNode.isKeyWordEnd()){
                    //发现敏感词
                    stringBuilder.append(REPLACE);
                    begin = ++position;
                    tempNode = rootNode;
                }else{
                    //检查到敏感词了,但是还在检查的途中。当字符串到了结尾,循环结束但是,没有添加结尾
                    position++;
                }
            }
            // position遍历越界仍未匹配到敏感词.此时begin未越界,可能begin后面后面还有没匹配的敏感词
            //所以不能退出循环和
            else {
                //没有检查到敏感词
                stringBuilder.append(text.charAt(begin));
                //下个位置
                position = ++begin;
                //重新指向root节点
                tempNode = rootNode;
            }

        }
        //所以我们要在循坏外加
        stringBuilder.append(text.substring(begin));
        return stringBuilder.toString();
    }

    //判断是否为符号,防止敏感词干扰。比如五角星啥的
    private boolean isSymbol(Character c){
        //是特殊符号,返回false.这个范围是东亚文字。特殊字符返回true。数字字母都不是特殊字符
        return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
    }


    private class TreeNode{
        //关键词结束标识
        private boolean isKeyWordEnd = false;

        //子节点。key下一个节点的字,value下一个节点
        private Map<Character,TreeNode> nextNode = new HashMap<>();

        public boolean isKeyWordEnd() {
            return isKeyWordEnd;
        }

        public void setKeyWordEnd(boolean keyWordEnd) {
            isKeyWordEnd = keyWordEnd;
        }

        //添加子节点
        public void addNextNode(Character c,TreeNode node){
            nextNode.put(c,node);
        }

        //获取子节点
        public TreeNode getNextNode(Character c){
            return nextNode.get(c);
        }
    }
}