0
点赞
收藏
分享

微信扫一扫

Java实现基于DFA算法的网站敏感词过滤



文章目录

  • 一、概述
  • 二、DFA算法
  • 1、什么是DFA算法
  • 2、Java实现DFA


一、概述

敏感词、文字过滤是一个网站必不可少的功能,高效的过滤算法是非常有必要的。针对过滤首先想到的可能是这样:

方案一、使用java里的String contains,逐个遍历敏感词:

String[] s = "广告,广告词,中奖".split(",");
String text = "讨厌的广告词";
boolean flag = false;
for (String s1 : s) {
	if (text.contains(s1)){
		flag = true;
		break;
	}
}
System.out.println(flag);

方案二、正则表达式:

System.out.println(text.matches(".*(广告|广告词|中奖).*"));

其实无论采取哪个方法,基本是换汤不换药。都是整体字符匹配,效率值得商榷。
那怎么办呢?DFA算法出场。

二、DFA算法

1、什么是DFA算法

DFA即Deterministic Finite Automaton,也就是确定有穷自动机,它是是通过event和当前的state得到下一个state,即event+state=nextstate。

对照到以上案例,查找和停止查找是动作,找没找到是状态,每一步的查找和结果决定下一步要不要继续。DFA算法在敏感词上应用的关键是构建敏感词库,如果我们把以上案例翻译成json表达如下:

{
    "isEnd": 0,
    "广": {
        "isEnd": 0,
        "告": {
            "isEnd": 1,
            "词": {
                "isEnd": 1
            }
        }
    },
    "中": {
        "isEnd": 0,
        "奖": {
            "isEnd": 1
        }
    }
}

查找过程如下:首先把text按字拆分,逐个字查找词库的key,先从“讨”开始,没有就下一个字“厌”,直到“广”,找到就判断isEnd,如果为1,说明匹配成功包含敏感词,如果为0,那就继续匹配“告”,直到isEnd=1为止。

匹配策略上,有两种。最小和最大匹配。最小则匹配【广告】,最大则需要匹配到底【广告词】

2、Java实现DFA

先加入fastjson坐标,查看敏感词库结构要用到

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>1.2.70</version>
</dependency>

import com.alibaba.fastjson.JSON;
import java.util.*;

/**
 * 敏感词处理DFA算法
 */
public class SensitiveWordUtil {

    //短匹配规则,如:敏感词库["广告","广告词"],语句:"我是广告词",匹配结果:我是[广告]
    public static final int SHORT_MATCH = 1;
    //长匹配规则,如:敏感词库["广告","广告词"],语句:"我是广告词",匹配结果:我是[广告词]
    public static final int LONG_MATCH = 2;

    /**
     * 敏感词库
     */
    public static HashMap sensitiveWordMap;


    /**
     * 初始化敏感词库
     * words:敏感词,多个用英文逗号分隔
     */
    private static void initSensitiveWordMap(String words) {
        String[] w = words.split(",");
        sensitiveWordMap = new HashMap(w.length);
        Map nowMap;
        for (String key : w) {
            nowMap = sensitiveWordMap;
            for (int i = 0; i < key.length(); i++) {
                //转换成char型
                char keyChar = key.charAt(i);
                //库中获取关键字
                Map wordMap = (Map)nowMap.get(keyChar);
                //如果不存在新建一个,并加入词库
                if (wordMap == null){
                    wordMap = new HashMap();
                    wordMap.put("isEnd", "0");
                    nowMap.put(keyChar, wordMap);
                }
                nowMap = wordMap;
                if (i == key.length() - 1) {
                    //最后一个
                    nowMap.put("isEnd", "1");
                }
            }
        }
    }

    /**
     * 判断文字是否包含敏感字符
     * @return 若包含返回true,否则返回false
     */
    public static boolean contains(String txt, int matchType) {
        for (int i = 0; i < txt.length(); i++) {
            int matchFlag = checkSensitiveWord(txt, i, matchType); //判断是否包含敏感字符
            if (matchFlag > 0) {    //大于0存在,返回true
                return true;
            }
        }
        return false;
    }


    /**
     * 沿着文本字符挨个往后检索文字中的敏感词
     */
    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));
                //指针沿着文本往后移动敏感词的长度
                //也就是一旦找到敏感词,加到列表后,越过这个词的字符,继续往下搜索
                //但是必须减1,因为for循环会自增,如果不减会造成下次循环跳格而忽略字符
                //这会造成严重误差
                i = i + length - 1;
            }
            //如果找不到,i就老老实实一个字一个字的往后移动,作为begin进行下一轮
        }

        return sensitiveWordList;
    }


    /**
     * 从第beginIndex个字符的位置,往后查找敏感词
     * 如果找到,返回敏感词字符的长度,不存在返回0
     * 这个长度用于找到后提取敏感词和后移指针,是个性能关注点
     */
    private static int checkSensitiveWord(String txt, int beginIndex, int matchType) {
        //敏感词结束标识位:用于敏感词只有1位的情况
        boolean flag = false;
        //匹配到的敏感字的个数,也就是敏感词长度
        int length = 0;
        char word;
        //从根Map开始查找
        Map nowMap = sensitiveWordMap;
        for (int i = beginIndex; i < txt.length(); i++) {
            //被判断语句的第i个字符开始
            word = txt.charAt(i);
            //获取指定key,并且将敏感库指针指向下级map
            nowMap = (Map) nowMap.get(word);
            if (nowMap != null) {//存在,则判断是否为最后一个
                //找到相应key,匹配长度+1
                length++;
                //如果为最后一个匹配规则,结束循环,返回匹配标识数
                if ("1".equals(nowMap.get("isEnd"))) {
                    //结束标志位为true
                    flag = true;
                    //短匹配,直接返回,长匹配还需继续查找
                    if (SHORT_MATCH == matchType) {
                        break;
                    }
                }
            } else {
                //敏感库不存在,直接中断
                break;
            }
        }
        if (length < 2 || !flag) {
            //长度必须大于等于1才算是词,字的话就不必这么折腾了
            length = 0;
        }
        return length;
    }


    public static void main(String[] args) {

        //初始化敏感词库
        SensitiveWordUtil.initSensitiveWordMap("广告,广告词,中奖");

        System.out.println("敏感词库结构:" + JSON.toJSONString(sensitiveWordMap));
        String string = "关于中奖广告的广告词筛选";
        System.out.println("被检测文本:"+string);
        System.out.println("待检测字数:" + string.length());

        //是否含有关键字
        boolean result = SensitiveWordUtil.contains(string,SensitiveWordUtil.LONG_MATCH);
        System.out.println("长匹配:"+result);
        result = SensitiveWordUtil.contains(string, SensitiveWordUtil.SHORT_MATCH);
        System.out.println("短匹配:"+result);

        //获取语句中的敏感词
        Set<String> set = SensitiveWordUtil.getSensitiveWord(string,SensitiveWordUtil.LONG_MATCH);
        System.out.println("长匹配到:" + set);
        set = SensitiveWordUtil.getSensitiveWord(string, SensitiveWordUtil.SHORT_MATCH);
        System.out.println("短匹配到:" + set);

    }

}

敏感词结构初始化后符合预期
检测和长短匹配有结果
匹配的敏感词列表正确


举报

相关推荐

0 条评论