1. 程式人生 > >DFA演算法實現敏感詞過濾

DFA演算法實現敏感詞過濾

DFA,即Deterministic Finite Automaton,也就是確定有窮自動機。


比如我們有愛戀、哈哈、感動、靜靜、發呆、太多、啦啦這幾個敏感詞,以上其實是通過HashMap組織成的樹形結構,比如我們通過“太”就可以知道這個字是否是“太多”這個詞的最後一個字,並且可以通過“太”獲取到“多”這個字且知道其是否為最後一個字。通過這些資訊我們就可以過濾掉敏感詞彙。

java例子:

package com.admin.util;

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

/**
 * 敏感詞處理 - DFA演算法實現
 *
 * @author jeff
 * @since 2018/3/15
 */
public class SensitiveWordUtil {

    /**
     * 敏感詞匹配規則
     */
    public static final int MinMatchTYpe = 1;      //最小匹配規則,如:敏感詞庫["中國","中國人"],語句:"我是中國人",匹配結果:我是[中國]人
    public static final int MaxMatchType = 2;      //最大匹配規則,如:敏感詞庫["中國","中國人"],語句:"我是中國人",匹配結果:我是[中國人]

    /**
     * 敏感詞集合
     */
    public static HashMap sensitiveWordMap;

    /**
     * 初始化敏感詞庫,構建DFA演算法模型
     *
     * @param sensitiveWordSet 敏感詞庫
     */
    public static synchronized void init(Set<String> sensitiveWordSet) {
        initSensitiveWordMap(sensitiveWordSet);
    }

    /**
     * 初始化敏感詞庫,構建DFA演算法模型
     *
     * @param sensitiveWordSet 敏感詞庫
     */
    private static void initSensitiveWordMap(Set<String> sensitiveWordSet) {
        //初始化敏感詞容器,減少擴容操作
        sensitiveWordMap = new HashMap(sensitiveWordSet.size());
        String key;
        Map nowMap;
        Map<String, String> newWorMap;
        //迭代sensitiveWordSet
        Iterator<String> iterator = sensitiveWordSet.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);
                //如果存在該key,直接賦值,用於下一個迴圈獲取
                if (wordMap != null) {
                    nowMap = (Map) wordMap;
                } else {
                    //不存在則,則構建一個map,同時將isEnd設定為0,因為他不是最後一個
                    newWorMap = new HashMap<>();
                    //不是最後一個
                    newWorMap.put("isEnd", "0");
                    nowMap.put(keyChar, newWorMap);
                    nowMap = newWorMap;
                }

                if (i == key.length() - 1) {
                    //最後一個
                    nowMap.put("isEnd", "1");
                }
            }
        }
    }

    /**
     * 判斷文字是否包含敏感字元
     *
     * @param txt       文字
     * @param matchType 匹配規則 1:最小匹配規則,2:最大匹配規則
     * @return 若包含返回true,否則返回false
     */
    public static boolean contains(String txt, int matchType) {
        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 若包含返回true,否則返回false
     */
    public static boolean contains(String txt) {
        return contains(txt, MaxMatchType);
    }

    /**
     * 獲取文字中的敏感詞
     *
     * @param txt       文字
     * @param matchType 匹配規則 1:最小匹配規則,2:最大匹配規則
     * @return
     */
    public 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 文字
     * @return
     */
    public static Set<String> getSensitiveWord(String txt) {
        return getSensitiveWord(txt, MaxMatchType);
    }

    /**
     * 替換敏感字字元
     *
     * @param txt         文字
     * @param replaceChar 替換的字元,匹配的敏感詞以字元逐個替換,如 語句:我愛中國人 敏感詞:中國人,替換字元:*, 替換結果:我愛***
     * @param matchType   敏感詞匹配規則
     * @return
     */
    public static String replaceSensitiveWord(String txt, char replaceChar, int matchType) {
        String resultTxt = txt;
        //獲取所有的敏感詞
        Set<String> set = getSensitiveWord(txt, matchType);
        Iterator<String> iterator = set.iterator();
        String word;
        String replaceString;
        while (iterator.hasNext()) {
            word = iterator.next();
            replaceString = getReplaceChars(replaceChar, word.length());
            resultTxt = resultTxt.replaceAll(word, replaceString);
        }

        return resultTxt;
    }

    /**
     * 替換敏感字字元
     *
     * @param txt         文字
     * @param replaceChar 替換的字元,匹配的敏感詞以字元逐個替換,如 語句:我愛中國人 敏感詞:中國人,替換字元:*, 替換結果:我愛***
     * @return
     */
    public static String replaceSensitiveWord(String txt, char replaceChar) {
        return replaceSensitiveWord(txt, replaceChar, MaxMatchType);
    }

    /**
     * 替換敏感字字元
     *
     * @param txt        文字
     * @param replaceStr 替換的字串,匹配的敏感詞以字元逐個替換,如 語句:我愛中國人 敏感詞:中國人,替換字串:[遮蔽],替換結果:我愛[遮蔽]
     * @param matchType  敏感詞匹配規則
     * @return
     */
    public 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();
            resultTxt = resultTxt.replaceAll(word, replaceStr);
        }

        return resultTxt;
    }

    /**
     * 替換敏感字字元
     *
     * @param txt        文字
     * @param replaceStr 替換的字串,匹配的敏感詞以字元逐個替換,如 語句:我愛中國人 敏感詞:中國人,替換字串:[遮蔽],替換結果:我愛[遮蔽]
     * @return
     */
    public static String replaceSensitiveWord(String txt, String replaceStr) {
        return replaceSensitiveWord(txt, replaceStr, MaxMatchType);
    }

    /**
     * 獲取替換字串
     *
     * @param replaceChar
     * @param length
     * @return
     */
    private static String getReplaceChars(char replaceChar, int length) {
        String resultReplace = String.valueOf(replaceChar);
        for (int i = 1; i < length; i++) {
            resultReplace += replaceChar;
        }

        return resultReplace;
    }

    /**
     * 檢查文字中是否包含敏感字元,檢查規則如下:<br>
     *
     * @param txt
     * @param beginIndex
     * @param matchType
     * @return 如果存在,則返回敏感詞字元的長度,不存在返回0
     */
    private static int checkSensitiveWord(String txt, int beginIndex, int matchType) {
        //敏感詞結束標識位:用於敏感詞只有1位的情況
        boolean flag = false;
        //匹配標識數預設為0
        int matchFlag = 0;
        char word;
        Map nowMap = sensitiveWordMap;
        for (int i = beginIndex; i < txt.length(); i++) {
            word = txt.charAt(i);
            //獲取指定key
            nowMap = (Map) nowMap.get(word);
            if (nowMap != null) {//存在,則判斷是否為最後一個
                //找到相應key,匹配標識+1
                matchFlag++;
                //如果為最後一個匹配規則,結束迴圈,返回匹配標識數
                if ("1".equals(nowMap.get("isEnd"))) {
                    //結束標誌位為true
                    flag = true;
                    //最小規則,直接返回,最大規則還需繼續查詢
                    if (MaxMatchType == 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("共產");
        sensitiveWordSet.add("政府");
        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);
        System.out.println(result);
        result = SensitiveWordUtil.contains(string, SensitiveWordUtil.MaxMatchType);
        System.out.println(result);

        //獲取語句中的敏感詞
        Set<String> set = SensitiveWordUtil.getSensitiveWord(string);
        System.out.println("語句中包含敏感詞的個數為:" + set.size() + "。包含:" + set);
        set = SensitiveWordUtil.getSensitiveWord(string, SensitiveWordUtil.MaxMatchType);
        System.out.println("語句中包含敏感詞的個數為:" + set.size() + "。包含:" + set);

        //替換語句中的敏感詞
        String filterStr = SensitiveWordUtil.replaceSensitiveWord(string, '*');
        System.out.println(filterStr);
        filterStr = SensitiveWordUtil.replaceSensitiveWord(string, '*', SensitiveWordUtil.MaxMatchType);
        System.out.println(filterStr);

        String filterStr2 = SensitiveWordUtil.replaceSensitiveWord(string, "[*敏感詞*]");
        System.out.println(filterStr2);
        filterStr2 = SensitiveWordUtil.replaceSensitiveWord(string, "[*敏感詞*]", SensitiveWordUtil.MaxMatchType);
        System.out.println(filterStr2);
    }

}

列印結果:

敏感詞的數量:3
待檢測語句字數:36
true
true
語句中包含敏感詞的個數為:5。包含:[萬歲, 萬萬歲, 共和國, 政府, 共產]
語句中包含敏感詞的個數為:5。包含:[萬歲, 萬萬歲, 共和國, 政府, 共產]
中華人民*****,偉大的**黨**,中央人民****。****萬**。
中華人民*****,偉大的**黨**,中央人民****。****萬**。
中華人民[*敏感詞*][*敏感詞*],偉大的[*敏感詞*]黨[*敏感詞*],中央人民[*敏感詞*][*敏感詞*]。[*敏感詞*][*敏感詞*]萬[*敏感詞*]。
中華人民[*敏感詞*][*敏感詞*],偉大的[*敏感詞*]黨[*敏感詞*],中央人民[*敏感詞*][*敏感詞*]。[*敏感詞*][*敏感詞*]萬[*敏感詞*]。

最大匹配原則驗證:

{={isEnd=0, ={={faqId=key_main_kcb_faq_004,isEnd=1}, faqId=key_main_kcb_faq_001, isEnd=1}},

說={吧={faqId=key_main_kcb_faq_003, isEnd=1}, isEnd=0},

您={說={faqId=key_main_kcb_faq_003, isEnd=1}, isEnd=0},

改={天={吧={faqId=key_main_kcb_faq_004, isEnd=1}, isEnd=0}, isEnd=0},

不={需={要={faqId=key_main_kcb_faq_002, isEnd=1}, isEnd=0}, isEnd=0}}

解釋:

問題:你好啊

匹配結果:綠色字型部分,完整都匹配上,faqId:key_main_kcb_faq_004

問題:你好

匹配結果:黃色字型部分,完整匹配上,faqId:key_main_kcb_faq_001

舉例:客戶說了一長段“你好啊啊啊啊”,就會匹配上綠色部分,因為dfa演算法最大匹配原則可以匹配到“你好啊”。