使用字典树实现敏感词过滤

项目中需要过滤敏感词,可以使用字典树来构造一个敏感词树,然后在树上对文本内容进行字符串匹配。

@Service
public class SensitiveWordService implements InitializingBean {
    private static Logger logger = LoggerFactory.getLogger(SensitiveWordService.class);
    @Override
    public void afterPropertiesSet() throws Exception{
        try{
            System.out.println(Thread.currentThread().getContextClassLoader());
            InputStream is =Thread.currentThread().getContextClassLoader().getResourceAsStream("SensitiveWord");
            InputStreamReader read = new InputStreamReader(is);
            BufferedReader bufferedReader = new BufferedReader(read);
            String lineTxt;
            while((lineTxt=bufferedReader.readLine())!=null){
                addWord(lineTxt.trim());
                System.out.println(lineTxt.trim());
            }
            read.close();
        }catch (Exception e){
            logger.error("读取敏感词文件失败"+e.getMessage());
        }
    }

    public class TreeNode{
        private boolean end=false;
        private Map,TreeNode> subNodes = new HashMap,TreeNode>();

        public void addSubNode(Character key,TreeNode node){
            subNodes.put(key,node);
        }
        TreeNode getSubNode(Character key){
            return subNodes.get(key);
        }
        boolean isEndWord(){
            return end;
        }
        void setEndWord(boolean end){
            this.end=end;
        }
    }
    private TreeNode root = new TreeNode();

    //增加关键词
    private void addWord(String lineTxt){
        TreeNode curNode = root;
        for(int i=0;ilength();i++){
            Character c = lineTxt.charAt(i);
            TreeNode node = curNode.getSubNode(c);
            if(node==null){
                node=new TreeNode();
                curNode.addSubNode(c,node);
            }
            curNode=node;
            if(i==lineTxt.length()-1){
                curNode.setEndWord(true);
            }
        }
    }

    //xxx *** 空格等迷惑过滤掉
    private boolean isSymbol(char c){
        int ic=(int)c;
        //东亚文字 0x2E80~0x9FFF
        return !CharUtils.isAsciiAlphanumeric(c) &&  (ic < 0x2E80 || ic > 0x9FFF);
    }

    public String filter(String text){//过滤字符串
        if(StringUtils.isBlank(text)){
            return text;
        }
        StringBuilder result = new StringBuilder();
        String replacement = "***";
        TreeNode curNode = root;
        int begin=0,position=0;

        while (position < text.length()){
            char c= text.charAt(position);
            if(isSymbol(c)){
                if(curNode==root){
                    result.append(c);
                    ++begin;
                }
                ++position;
                continue;
            }

            curNode=curNode.getSubNode(c);
            if(curNode==null){
                result.append(text.charAt(begin));
                ++begin;
                position = begin;
                curNode = root;
            }
            else if(curNode.isEndWord()){//发现敏感词
                result.append((replacement));
                ++position;
                begin=position;
                curNode = root;

            }
            else{
                ++position;
            }
        }

        result.append(text.substring(begin));
        return result.toString();
    }
/*
    public static void main(String[] argv){
        SensitiveWordService s = new SensitiveWordService();
        s.addWord("色情");
        //s.addWord("赌博");
        //System.out.println(s.filter("你好色情"));
        System.out.println(s.filter("北京欢迎你你好色情"));
    }
    */
}

进行优化后可以使用ac自动机,也就是在树上做KMP。

(1)KMP算法

    KMP算法和暴力匹配区别在于KMP算法中i不需要回溯且j回溯到next[j]的位置即可,主要是通过next数组来实现的。

    next数组:只根据要匹配的子串生成,

    next[i]表示在匹配的子串中第i个字符前面的那一坨字符串的一个指标,这个指标是最长的前缀和后缀相等的情况的那个长度,前缀不包括最后一个字符,后缀不包括第一个字符。

    比如a   a   a   a   b ,    1    2    3    1    2    3    d

         -1   0   1   2   3 ,   -1    0    0    0    1    2    3

next[4]=3,position[4]的字符为b,那么它的前缀为"aaaa",最长的前缀aaa和最长后缀aaa相同,长度为3。

题目:在S中查找P字符串是否存在

public int[] getNextArray(String ms) {
    if (ms.length() == 1) return new int[] {-1};
    int[] next = new int[ms.length()];
    next[0] = -1;
    next[1] = 0;
    int pos = 2;
    int cn = 0;
    while (pos < ms.length()) {
        if (ms.charAt(pos - 1) == ms.charAt(cn)) {
            next[pos++] = ++cn;
        } else if (cn > 0) {
            cn = next[cn];
        } else {
            next[pos++] = 0;
        }
    }
    return next;
}

public void kmp(String s, String p) {
    int next[] = getNextArray(p);
    int i = 0, j = 0;
    while (i < s.length() && j < p.length()) {
        if (j == -1 || s.charAt(i) == p.charAt(j)) {
            ++i; ++j;
        } else {
            j = next[j];
        }
    }
    if (j == p.length()) {
        System.out.println("s中匹配到了字串p");
        return;
    }
    System.out.println("没有匹配到");
}

(2)ac自动机(在树上进行KMP)

   










你可能感兴趣的:(使用字典树实现敏感词过滤)