基于DFA-前缀树的敏感词汇过滤算法(项目实用)

在敏感词汇过滤这块,不同的算法所造成的性能差异是非常大的,选择一个合适的算法非常重要。因为以前做算法的时候做过类似前缀树的字符串匹配之类的算法,所以一开始就打算用前缀树做的,后面了解了一下DFA的相关算法原理,其实用在敏感词汇过滤这块,主要还是前缀树的应用。

这个算法最原始的实现我是采用的建立树结构的方式,发现性能还是不佳,最后借鉴了一些基于HashMap构造前缀树的方法来实现。算是使用比较广泛的算法了。在这里进行了完整的封装,并且做了一些小的优化。

这个算法并不是目前最好的敏感词过滤算法,但是比较容易理解,而且性能也不差,所以小的项目用这个也比较合适了。


0x01.关于DFA与前缀树

  • DFA(Deterministic Finite Automaton):确定有穷自动机
  • 通俗理解DFA就是:从一个状态通过一系列的事件转换到另一个状态。
  • 建立前缀树进行敏感词匹配的过程正是DFA的一种应用。
  • 经过测试,使用HashMap建立的前缀树,确实比实际构造树结构的性能要好。

0x02.该算法的优缺点

  • 优点:性能比较好,思想及实现都比较简单。
  • 缺点:整个前缀树加载到内存中,如果敏感词比较多的话,那么消耗的内存会比较多。

0x03.完整代码(已封装好)

  • 非静态方法,已封装好,创建对象后直接调用。

  • 唯一需要修改的地方:具体敏感词汇的路径

  • 具体注释在代码中。

  • 提供三个方法:

    • getSensitiveWordNums:获取文本中敏感词汇的个数。
    • getSensitiveWordSet:获取文本中所有包含的敏感词汇。
    • replaceAllSensitiveWord:将文本中的敏感词汇替换成指定的词汇。
  • 参数说明:

    • MIN_CONTAIN: 最少包含多少字才会被认定为敏感词。
    • minMatchTYpe: 单次匹配到一个敏感词汇后不继续匹配。
    • maxMatchType: 一直匹配直到文本结束。
import java.io.*;
import java.util.*;


@SuppressWarnings({ "rawtypes", "unchecked" })
public class SensitiveWordUtils {
    private String ENCODING = "UTF-8";//文件的字符编码
    private String PATH_RELATIVE="/src/main/resources/static/senc.txt";//敏感词汇相对项目的路径
    private HashMap sensitiveWordMap;//敏感词汇树
    private Integer MIN_CONTAIN=2;//包含敏感词的最多字数
    public static int minMatchTYpe = 1;      //最小匹配规则
    public static int maxMatchType = 2;      //最大匹配规则
    /**
     * 获取文本中敏感词汇的个数
     * @param txt 要检测的文本
     * @param beginIndex 开始的地方
     * @param matchType 匹配的规则
     * @return 敏感词汇的长度
     */
    public Integer getSensitiveWordNums(String txt,int beginIndex,int matchType){
        Set<String> wordSet = readSensitiveWordFromFile();
        addSensitiveWordToHashMap(wordSet);
        Integer result=0;
        for(int i = 0 ; i < txt.length() ; i++){
            //判断是否包含敏感字符
            int length = CheckSensitiveWord(txt, i, matchType);
            if(length > 0){    //存在,加入list中
                result++;
                i = i + length - 1;
            }
        }
        return result;
    }
    /**
     *
     * @param txt 检测的文本
     * @param matchType 匹配的规则
     * @return Set 敏感词汇集合
     */
    public Set<String> getSensitiveWordSet(String txt , int matchType){
        Set<String> wordSet = readSensitiveWordFromFile();
        addSensitiveWordToHashMap(wordSet);
        return getSensitiveWord(txt,matchType);
    }
    /**
     *
     * @param txt
     * @param matchType
     * @param replaceChar 替换的词
     * @return 处理后的文本
     */
    public String replaceAllSensitiveWord(String txt,int matchType,String replaceChar){
        Set<String> wordSet = readSensitiveWordFromFile();
        addSensitiveWordToHashMap(wordSet);
        return replaceSensitiveWord(txt,matchType,replaceChar);
    }


    /**
     * 从文件中读取敏感词
     * @return 敏感词的Set集合
     */
    private Set<String> readSensitiveWordFromFile(){
        Set<String> wordSet = null;
        //获取项目路径
        String projectPath = System.getProperty("user.dir");
        //拼接得到敏感词汇的路径
        String path=projectPath + PATH_RELATIVE;
        File file = new File(path);
        try {
            //指定编码读取文件
            InputStreamReader read = new InputStreamReader(new FileInputStream(file), ENCODING);
            //判断文件是否存在
            if (file.isFile() && file.exists()) {
                //初始化wordSet
                wordSet = new HashSet<String>();
                //br用于读取文件的每一行
                BufferedReader br = new BufferedReader(read);
                String txt = null;
                while ((txt = br.readLine()) != null) {
                    wordSet.add(txt);
                }
                br.close();
            }else{
                throw new Exception("文件错误或文件不存在!");
            }
            read.close();

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return wordSet;
    }

    //处理从文件得到的敏感词汇表,构造敏感词树

    /**
     * 将敏感词汇处理成敏感词汇树
     * @param keyWordSet 敏感词汇表
     */
    private void addSensitiveWordToHashMap(Set<String> keyWordSet){
        sensitiveWordMap = new HashMap(keyWordSet.size());
        String key = null;//读取的每一个关键字
        Map nowMap = null;//当前处理的map
        Map<String, String> newWorMap = null;//新建的map
        //用keyWordSet迭代器进行遍历
        Iterator<String> iterator = keyWordSet.iterator();
        while(iterator.hasNext()){
            key = iterator.next();
            nowMap = sensitiveWordMap;
            //遍历敏感词的每一位
            for(int i = 0 ; i < key.length() ; i++){
                //将一位转换位char类型
                char keyChar = key.charAt(i);
                Object wordMap = nowMap.get(keyChar);
                if(wordMap != null){  //如果存在该key,直接赋值
                    nowMap = (Map) wordMap;
                }else{   //不存在则,则构建一个map,同时将isEnd设置为0
                    newWorMap = new HashMap<String,String>();
                    newWorMap.put("isEnd", "0");     //不是最后一个
                    nowMap.put(keyChar, newWorMap);
                    nowMap = newWorMap;
                }
                if(i == key.length() - 1){
                    nowMap.put("isEnd", "1");    //最后一个
                }
            }
        }
    }

    /**
     * 获取文本中敏感词汇的个数
     * @param txt 要检测的文本
     * @param beginIndex 开始的地方
     * @param matchType 匹配的规则
     * @return 敏感词汇的长度
     */
    private int CheckSensitiveWord(String txt,int beginIndex,int matchType){
        boolean  flag = false;
        int matchFlag = 0;     //匹配标识数默认为0
        Map nowMap = sensitiveWordMap;
        char word = 0;
        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(minMatchTYpe == matchType){    //最小规则,直接返回,最大规则还需继续查找
                        break;
                    }
                }
            }else{
                break;
            }
        }
        return (matchFlag>=MIN_CONTAIN&&flag)?matchFlag:0;
    }

    /**
     *
     * @param txt 检测的文本
     * @param matchType 匹配的规则
     * @return Set 敏感词汇集合
     */
    private Set<String> getSensitiveWord(String txt , int matchType){
        Set<String> sensitiveWordList = new HashSet<String>();
        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;
            }
        }
        return sensitiveWordList;
    }
    //判断文字是否包含敏感字符

    /**
     * 检测是否包含敏感词
     * @param txt 检测的文本
     * @param matchType 匹配的规则
     * @return
     */
    public boolean isContaintSensitiveWord(String txt,int matchType){
        boolean flag = false;
        for(int i = 0 ; i < txt.length() ; i++){
            //判断是否包含敏感字符
            int matchFlag = this.CheckSensitiveWord(txt, i, matchType);
            //存在
            if(matchFlag > 0){
                flag = true;
            }
        }
        return flag;
    }

    /**
     *
     * @param txt
     * @param matchType
     * @param replaceChar 替换的词
     * @return 处理后的文本
     */
    private String replaceSensitiveWord(String txt,int matchType,String replaceChar){
        String resultTxt = txt;
        Set<String> set = getSensitiveWord(txt, matchType);
        Iterator<String> iterator = set.iterator();
        String word = null;
        String replaceString = null;
        while (iterator.hasNext()) {
            word = iterator.next();
            replaceString = getReplaceChars(replaceChar, word.length());
            resultTxt = resultTxt.replaceAll(word, replaceString);
        }

        return resultTxt;
    }

    //替换原本字符串
    private String getReplaceChars(String replaceChar,int length){
        String resultReplace = replaceChar;
        for(int i = 1 ; i < length ; i++){
            resultReplace += replaceChar;
        }
        return resultReplace;
    }
}

0x04.主要思路

  • 读取文件里的敏感词汇到set集合中。
  • 将set集合里的敏感词汇转换为HashMap形式的前缀树。
  • 读取前缀树进行文本敏感词汇的判断。

GitHub地址:https://github.com/ATFWUS/Project-Algorithm/tree/master/Sensitive-word-filtering

你可能感兴趣的:(项目实战技巧篇)