java 实现敏感词过滤DFA算法

1. 前言

敏感词文字过滤是一个网站必不可少的功能,如何设计一个好的、高效的过滤算法是非常有必要的。作为一般开发人员来说首先考虑的肯定是简单的匹配,这样是可以实现功能,但效率比较慢,在高级一点的就是正则表达式,比前一个好一点,但终究还是一丘之貉,非常遗憾,两种方法都不可取。当然,在我意识里没有我也没有认知到那个算法可以解决问题,但是百度知道,以下就是学习的DFA算法简单介绍和功能实现。

2. DFA算法来历

DFA全称为:Deterministic Finite Automaton,即确定有穷自动机。
其特征为:有一个有限状态集合和一些从一个状态通向另一个状态的边,每条边上标记有一个符号,其中一个状态是初态,某些状态是终态。
但不同于不确定的有限自动机,DFA中不会有从同一状态出发的两条边标志有相同的符号。

具体功能:

  1. 将敏感词汇保存在map中。
  2. 对敏感词汇进行过滤,将敏感词变为“*”。
  3. 对无意义符号进行忽略处理。
敏感词数据结构:
{  王:{
            isEnd: false
            八:{
                    isEnd:false
                    蛋:{
                              isEnd:true
                       }
                 }
       }
}
无意义符号数据结构:
{
  "@":Null (空结构体)
}

3. 上代码

封装工具类:

public class SensitiveWordUtil {

    /**
     * 敏感词匹配规则
     */
    private static final int MinMatchTYpe = 1;      //最小匹配规则,如:敏感词库["我是人","我是人么"],语句:"你猜我是人么",匹配结果:我是[我是人]么
    public static final int MaxMatchType = 2;      //最大匹配规则,如:敏感词库["我是人","我是人么"],语句:"你猜我是人么",匹配结果:我是[我是人么]

    /**
     * 敏感词集合
     */
    private static HashMap sensitiveWordMap;

    /**
     * 初始化敏感词库,构建DFA算法模型
     *
     * @param sensitiveWordSet set
     */
    private static synchronized void init(Set<String> sensitiveWordSet) {
        sensitiveWordMap = new HashMap(sensitiveWordSet.size()); //初始化敏感词容器,减少扩容操作
        String key;
        Map nowMap;
        Map<String, String> newWorMap;
        for (String s : sensitiveWordSet) { //迭代sensitiveWordSet
            key = s; //关键字
            nowMap = sensitiveWordMap;
            for (int i = 0; i < key.length(); i++) {
                char keyChar = key.charAt(i);  //转换成char型
                Object wordMap = nowMap.get(keyChar); //库中获取关键字
                if (wordMap != null) { //如果存在该key,直接赋值,用于下一个循环获取
                    nowMap = (Map) wordMap;
                } else {
                    newWorMap = new HashMap<>(); //不存在则,则构建一个map,同时将isEnd设置为0,因为他不是最后一个
                    newWorMap.put("isEnd", "0"); //不是最后一个
                    nowMap.put(keyChar, newWorMap);
                    nowMap = newWorMap;
                }
                if (i == key.length() - 1) {
                    nowMap.put("isEnd", "1");  //最后一个
                }
            }
        }
    }

    /**
     * 判断文字是否包含敏感字符
     * @param txt 要过滤的文章
     * @param matchType 过滤规则
     * @param list 脏词库
     * @return
     */
    public static boolean contains(String txt, int matchType, List<String> list) {
        if (sensitiveWordMap.size() <= 0){
            System.out.println("开始初始化平台脏数据");
            if (list.size() > 0){
                Set<String> sensitiveWordSet = new HashSet<>(list);
                System.out.println("set: " + sensitiveWordSet);
                SensitiveWordUtil.init(sensitiveWordSet);
                System.out.println("初始化平台脏数据完成");
            }
            System.out.println("初始化数据源未能获取到");
        }
        boolean flag = false;
        for (int i = 0; i < txt.length(); i++) {
            int matchFlag = checkSensitiveWord(txt, i, matchType); //判断是否包含敏感字符
            if (matchFlag > 0) {    //大于0存在,返回true
                flag = true;
            }
        }
        return flag;
    }

    /**
     * 获取文字中的敏感词
     *
     * @param txt       文字
     * @return 敏感词
     */
    private static Set<String> getSensitiveWord(String txt, int matchType) {
        Set<String> sensitiveWordList = new HashSet<>();
        for (int i = 0; i < txt.length(); i++) {
            int length = checkSensitiveWord(txt, i, matchType); //判断是否包含敏感字符
            if (length > 0) { //存在,加入list中
                sensitiveWordList.add(txt.substring(i, i + length));
                i = i + length - 1; //减1的原因,是因为for会自增
            }
        }
        return sensitiveWordList;
    }

    /**
     * 替换敏感字字符
     *
     * @param txt        文本
     * @param replaceStr 替换的字符串,匹配的敏感词以字符逐个替换,如 语句:你猜我是人么 敏感词:是人么,替换字符串:**,替换结果:你猜我***
     * @return 敏感字字符结果
     */
    private static String replaceSensitiveWord(String txt, String replaceStr, int matchType) {
        String resultTxt = txt;
        Set<String> set = getSensitiveWord(txt, matchType); //获取所有的敏感词
        Iterator<String> iterator = set.iterator();
        String word;
        while (iterator.hasNext()) {
            word = iterator.next();
            for (int i = 0; i < word.length() - 1; i++) {
                replaceStr += replaceStr;
            }
            resultTxt = resultTxt.replaceAll(word, replaceStr);
        }
        return resultTxt;
    }

    /**
     * 检查文字中是否包含敏感字符
     *
     * @return 如果存在,则返回敏感词字符的长度,不存在返回0
     */
    private static int checkSensitiveWord(String txt, int beginIndex, int matchType) {
        boolean flag = false;  //敏感词结束标识位:用于敏感词只有1位的情况
        int matchFlag = 0; //匹配标识数默认为0
        char word;
        Map nowMap = sensitiveWordMap;
        for (int i = beginIndex; i < txt.length(); i++) {
            word = txt.charAt(i);
            nowMap = (Map) nowMap.get(word); //获取指定key
            if (nowMap != null) { //存在,则判断是否为最后一个
                matchFlag++; //找到相应key,匹配标识+1
                if ("1".equals(nowMap.get("isEnd"))) { //如果为最后一个匹配规则,结束循环,返回匹配标识数
                    flag = true; //结束标志位为true
                    if (SensitiveWordUtil.MinMatchTYpe == matchType) break; //最小规则,直接返回,最大规则还需继续查找
                }
            } else break; //不存在,直接返回
        }
        if (matchFlag < 2 || !flag) { //长度必须大于等于1,为词
            matchFlag = 0;
        }
        return matchFlag;
    }

    /*public static void main(String[] args) {

        Set<String> sensitiveWordSet = new HashSet<>();
        sensitiveWordSet.add("你妈");
        sensitiveWordSet.add("有病");
        //初始化敏感词库
        SensitiveWordUtil.init(sensitiveWordSet);

        System.out.println("敏感词库的数量:" + SensitiveWordUtil.sensitiveWordMap.size());
        String string = "我说你这孩子是不是有病,你妈的。";
        System.out.println("准备检查的语句:" + string.length() + "个字");

        //是否含有关键字
        boolean result = SensitiveWordUtil.contains(string, SensitiveWordUtil.MaxMatchType);
        System.out.println("检查出了敏感词?" + result);

        //获取语句中的敏感词
        Set<String> set = SensitiveWordUtil.getSensitiveWord(string, SensitiveWordUtil.MaxMatchType);
        System.out.println("语句中包含敏感词的个数为:" + set.size() + ",包含:" + set);

        //替换语句中的敏感词
        String s = "*";
        String filterStr = SensitiveWordUtil.replaceSensitiveWord(string, s, SensitiveWordUtil.MaxMatchType);
        System.out.println("001号已为你重新组织了你的语言:" + filterStr);

    }*/
}

调用方如下:

private boolean verification(String title) {

        List<String> list = JSON.parseArray((String) redisTemplate.opsForValue().get("curse:list"), String.class);

        /*
         * 参数一:是我们要检验的字符串
         *
         * 参数二:是我们过滤的规则
         *
         * 参数三:是我们的脏词库(这里我直接从redis取出了)
         */
        return SensitiveWordUtil.contains(title, SensitiveWordUtil.MaxMatchType, list);
    }

4. 总结

我这里脏词库是调用方传入过来的,也就没有去做统一的库直接初始化操作等。。

代码工具整体就这样子,简单的几种过滤方法。
有看那些不懂欢迎评论去留言。

作者:Teddy (公众号:鸡仓故事汇)
ok!到这里就大功告成,小编(Teddy)在这里先感谢大家的到来。
虽然不是太详细,小编已经很努力,给小编来个一键三连(点赞,关注,收藏),小编会越来越努力。。。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值