利用DFA算法实现敏感词过滤(java)

1、问题描述

当初找实习面试时一面试官问 一段话如何判断其中是否包含敏感词,当时经验跟能力不足,也只能想到使用正则表达式或者双重循环蛮力实现,但是肯定不能满足要求。现在工作几年了突然想起来,想想当初面试官给讲述的方法并参考网上文章加上自己的理解还是将功能做出来了。

2、解决方法

(1)、DFA简介

DFA即Deterministic Finite Automaton,也就是确定有穷自动机,它是是通过event和当前的state得到下一个state,即event+state=nextstate,状态转化如下图:
复制的网上原图

(2)、实现原理-构建敏感词库

可以将所有敏感词构成多棵树,这样在对文本中每个字进行敏感词匹配时只需检索以这个字开头的树即可,大大的减少了检索的范围。

例如:敏感词为:中国人,中国男人,中国好人,构建树如下图:
在这里插入图片描述

构建HashMap结构如下:
{
    "中": {
        "isEnd": 0,
        "国": {
            "男": {
                "人": {
                    "isEnd": 1
                },
                "isEnd": 0
            },
            "人": {
                "isEnd": 1
            },
            "isEnd": 0,
            "好": {
                "人": {
                    "isEnd": 1
                },
                "isEnd": 0
            }
        }
    }
}
构建过程:

1、在HashMap中判断是否存在“中”,如果不存在,则构建以“中”开头的树,跳至3;
2、如果存在,则取以“中”开头的树为当前HashMap,跳至1,继续遍历“国”、“人”;
3、判断该字是否是该词的最后一个字,如果是标志位(isEnd)设为1,否则设为0

代码如下:
	/**
     * 将敏感词加入HashMap
     */
    private void addSensitiveWordsToHashMap(Set<String> sensitiveWords) {
        sensitiveWordsMap = new HashMap(sensitiveWords.size());
        String word = null;
        Map childMap = null;
        Map<String,String> newWordMap = null;
        Iterator<String> iterator = sensitiveWords.iterator();
        while (iterator.hasNext()){
            //关键字
            word = iterator.next();
            childMap = sensitiveWordsMap;
            //遍历该关键字
            for (int i = 0; i < word.length();i++){
                char key = word.charAt(i);
                Object wordMap = childMap.get(key);
                if(wordMap != null){
                    childMap = (Map)wordMap;
                } else {
                    newWordMap = new HashMap<>();
                    newWordMap.put("isEnd", "0");
                    childMap.put(key, newWordMap);
                    //指向当前map,继续遍历
                    childMap = newWordMap;
                }

                if(i == word.length() - 1){
                    //最后一个
                    childMap.put("isEnd", "1");
                }
            }
        }
    }

(3)实现原理-匹配敏感词

以匹配“中国人”为例

匹配过程:

1、获取以“中”开头的map,如果map为空,则不是敏感词,继续跳转到1依次遍历“国”、“人”;
2、如果不为空,则匹配标识+1,并通过isEnd判断该词匹配是否结束,跳转至3;
3、如果isEnd=1,则表示已结束,表示匹配到该敏感词,则包含敏感词,匹配标识则为敏感词长度,否则继续跳转到1依次遍历“国”、“人”

因此,“中国人”可以匹配到,而“中国坏人”则匹配不到

代码如下:
 	/**
     * 校验是否包含敏感词
     * @param txt 待判断文本
     * @param start 起始位置
     * @param matchType 匹配类型: 1 最小匹配原则;2 最大匹配原则
     * @return 大于0表示包含敏感词且表示敏感词匹配长度,否则不包含
     */
    private static Integer checkSensitiveWords(String txt, Integer start, Integer matchType) {
        //敏感词结束标志
        Boolean flag = false;
        char word;
        //匹配标识数默认为0
        Integer matchFlag = 0;
        Map childMap = sensitiveWordsMap;
        System.out.println(sensitiveWordsMap);
        for(int i = start;i < txt.length();i++){
            word = txt.charAt(i);
            childMap = (Map)childMap.get(word);
            if(childMap == null){
                //不存在该字打头的敏感词
                break;
            } else {
                //匹配标识+1
                matchFlag++;
                if("1".equals(childMap.get("isEnd"))){
                    flag = true;
                    if(minMatchType.equals(matchType)){
                        //最小匹配规则则跳出,否则继续匹配
                        break;
                    }
                }
            }
        }
        if(matchFlag < 2 || !flag){
            //匹配长度需大于2才为词,并且敏感词已结束
            matchFlag = 0;
        }
        return matchFlag;
    }

(4)测试

代码如下:
public static void main(String[] args) {
        String txt = "当初实习面试问的一个问题就是检索一段话中是否包含敏感词,当初只能想到双重循环蛮力实现," +
                "现在工作近三年突然回想起,还是完成了敏感词替换代码。虽然感觉很操蛋,但还是很牛逼,简单列举几个敏感词," +
                "你妹的别给假钞!";
        Boolean containsSensitiveWords = SensitiveWordsFilter.isContainsSensitiveWords(txt, 1);
        System.out.println("Is contains sensitive word : " + containsSensitiveWords);
    }

####运行结果:
在这里插入图片描述

3、相关代码

(1)构建敏感词库

package SensitiveWords;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.*;

/**
 * @description: 构建敏感词库
 *  将敏感词从文件读取放入HashMap
 * @author: zrk
 * @create: 2019/1/3
 */
public class SensitiveWordsInit {

    public HashMap sensitiveWordsMap;

    /**
     * 初始化敏感词HashMap
     */
    public Map initSensitiveWords(){
        try {
            //读取敏感词文件
            Set<String> sensitiveWordSet = readSensitiveWordsFile();
            //将敏感词加入HashMap
            addSensitiveWordsToHashMap(sensitiveWordSet);

        } catch (Exception e){
            e.printStackTrace();
        }
        return sensitiveWordsMap;
    }

    /**
     * 将敏感词加入HashMap
     */
    private void addSensitiveWordsToHashMap(Set<String> sensitiveWords) {
        sensitiveWordsMap = new HashMap(sensitiveWords.size());

        String word = null;
        Map childMap = null;
        Map<String,String> newWordMap = null;
        Iterator<String> iterator = sensitiveWords.iterator();
        while (iterator.hasNext()){
            //关键字
            word = iterator.next();
            childMap = sensitiveWordsMap;
            //遍历该关键字
            for (int i = 0; i < word.length();i++){
                char key = word.charAt(i);
                Object wordMap = childMap.get(key);
                if(wordMap != null){
                    childMap = (Map)wordMap;
                } else {
                    newWordMap = new HashMap<>();
                    newWordMap.put("isEnd", "0");
                    childMap.put(key, newWordMap);
                    //指向当前map,继续遍历
                    childMap = newWordMap;
                }

                if(i == word.length() - 1){
                    //最后一个
                    childMap.put("isEnd", "1");
                }
            }
        }
    }

    /**
     * 读取敏感词文件
     */
    private Set<String> readSensitiveWordsFile() {

        File file = new File("D:\\SensitiveWords.txt");
        InputStreamReader isReader = null;
        try {
            isReader = new InputStreamReader(new FileInputStream(file), "GBK");
            if(file.exists() && file.isFile()){
                BufferedReader br = new BufferedReader(isReader);
                String s = br.readLine();
                if(s != null && !"".equals(s)){
                    Set<String> set = new HashSet<>();
                    Collections.addAll(set, s.split(","));
                    return set;
                }
            } else {
                System.out.println("读取文件的不存在!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(isReader != null) {
                try {
                    isReader.close();
                } catch ( Exception e){

                }
            }
        }
        return null;
    }
}

(2)敏感词过滤器

package SensitiveWords;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * @description: 敏感词过滤器
 * @author: zrk
 * @create: 2019/1/3
 */
public class SensitiveWordsFilter {

    private static Map<String, String> sensitiveWordsMap = null;
    private static Integer minMatchType = 1;      //最小匹配规则

    static {
        sensitiveWordsMap = new SensitiveWordsInit().initSensitiveWords();
    }

    /**
     * 判断文本是否包含关键字
     * @param txt 待判断文本
     * @param matchType 匹配类型: 1 最小匹配原则;2 最大匹配原则
     * @return true 包含;false 不包含
     */
    public static Boolean isContainsSensitiveWords(String txt, Integer matchType){
        if(txt == null || "".equals(txt)){
            return false;
        }
        for(int i = 0; i < txt.length();i++){
            Integer matchFlag = checkSensitiveWords(txt, i , matchType);
            if(matchFlag > 0){
                return true;
            }
        }
        return false;
    }

    /**
     * 校验是否包含敏感词
     * @param txt 待判断文本
     * @param start 起始位置
     * @param matchType 匹配类型: 1 最小匹配原则;2 最大匹配原则
     * @return 大于0表示包含敏感词且表示敏感词匹配长度,否则不包含
     */
    private static Integer checkSensitiveWords(String txt, Integer start, Integer matchType) {
        //敏感词结束标志
        Boolean flag = false;
        char word;
        //匹配标识数默认为0
        Integer matchFlag = 0;
        Map childMap = sensitiveWordsMap;
        for(int i = start;i < txt.length();i++){
            word = txt.charAt(i);
            childMap = (Map)childMap.get(word);
            if(childMap == null){
                //不存在该字打头的敏感词
                break;
            } else {
                //匹配标识+1
                matchFlag++;
                if("1".equals(childMap.get("isEnd"))){
                    flag = true;
                    if(minMatchType.equals(matchType)){
                        //最小匹配规则则跳出,否则继续匹配
                        break;
                    }
                }
            }
        }
        if(matchFlag < 2 || !flag){
            //匹配长度需大于2才为词,并且敏感词已结束
            matchFlag = 0;
        }
        return matchFlag;
    }

    /**
     * 获取所有敏感词
     * @param txt 待判断文本
     * @param matchType 匹配类型: 1 最小匹配原则;2 最大匹配原则
     * @return
     */
    public static Set<String> getSensitiveWords(String txt,Integer matchType){
        Set<String> sensitiveWords = new HashSet<>();

        for(int i = 0;i < txt.length();i++){
            Integer length = checkSensitiveWords(txt, i, matchType);
            if(length > 0){
                sensitiveWords.add(txt.substring(i, i + length));
                //循环i会+1,所以需-1
                i = i + length - 1;
            }
        }
        return sensitiveWords;
    }

    /**
     * 替换敏感词
     * @param txt 文本
     * @param matchType 匹配类型: 1 最小匹配原则;2 最大匹配原则
     * @param replaceStr 替换字符
     * @return 处理后的文本
     */
    public static String replaceSensitiveWords(String txt, Integer matchType, String replaceStr){
        if(txt == null || "".equals(txt)){
            return txt;
        }
        //获取所有敏感词
        Set<String> sensitiveWords = getSensitiveWords(txt, matchType);
        Iterator<String> iterator = sensitiveWords.iterator();
        String replaceString = "";
        while (iterator.hasNext()){
            String sWord = iterator.next();
            replaceString = getReplaceString(replaceStr, sWord.length());
            txt = txt.replaceAll(sWord, replaceString);
        }
        return txt;
    }

    private static String getReplaceString(String replaceStr, Integer length) {
        if(replaceStr == null){
            replaceStr = "*";
        }
        StringBuffer replaceString = new StringBuffer();
        for(int i = 0;i < length;i++){
            replaceString.append(replaceStr);
        }
        return replaceString.toString();
    }
}

(3)测试类

package SensitiveWords;

/**
 * @description:
 * @author: zrk
 * @create: 2019/1/3
 */
public class SensitiveWordsTest {
    public static void main(String[] args) {
        String txt = "当初实习面试问的一个问题就是检索一段话中是否包含敏感词,当初只能想到双重循环蛮力实现," +
                "现在工作近三年突然回想起,还是完成了敏感词替换代码。虽然感觉很操蛋,但还是很牛逼,简单列举几个敏感词," +
                "你妹的别给假钞!";
//        Boolean containsSensitiveWords = SensitiveWordsFilter.isContainsSensitiveWords(txt, 1);
//        System.out.println("Is contains sensitive word : " + containsSensitiveWords);
//        Set<String> sensitiveWords = SensitiveWordsFilter.getSensitiveWords(txt, 1);
//        System.out.println("All sensitive words : " + sensitiveWords);
        Long beginTime = System.currentTimeMillis();
        System.out.println("After replace sensitive words : " + SensitiveWordsFilter.replaceSensitiveWords(txt, 1, null));
        Long endTime = System.currentTimeMillis();
        System.out.println("cost : " + (endTime - beginTime));
    }
}

(4)敏感词文件

中国人,中国男人,中国好人,牛逼,操蛋,你妹的,摇头丸,白粉,冰毒,海洛因,假钞,人民币,假币

(5)运行结果

After replace sensitive words : 当初实习面试问的一个问题就是检索一段话中是否包含敏感词,当初只能想到双重循环蛮力实现,现在工作近三年突然回想起,还是完成了敏感词替换代码。虽然感觉很**,但还是很**,简单列举几个敏感词,***别给**!
cost : 5

4.总结

方法参考与网上,根据自己的理解整理,代码也进行了很多参考,好的方法值得拿来分享!

参考链接:https://blog.csdn.net/chenssy/article/details/26961957

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值