sensitive-word 敏感词之 DFA 算法(Trie Tree 算法)详解

news/2024/7/10 19:57:18 标签: word, 开发语言, 安全, github, 开源

拓展阅读

敏感词工具实现思路

DFA 算法讲解

敏感词库优化流程

java 如何实现开箱即用的敏感词控台服务?

各大平台连敏感词库都没有的吗?

v0.10.0-脏词分类标签初步支持

v0.11.0-敏感词新特性:忽略无意义的字符,词标签字典

v0.12.0-敏感词/脏词词标签能力进一步增强

背景

想实现一个基于敏感词库的敏感词工具。

遍历匹配

发现如果是逐个字符遍历的话,效率实在是太低。

这里我首先想到了两种算法:

KMP 算法

BF 暴力匹配算法

当然单纯只是匹配,其实性能依然非常的低。

正则表达式

当然还有一种方式就是基于正则表达式,个人感觉这种性能也比较差。

正则表达式

直接查了下资料,可以使用 DFA 算法来解决这个问题。

DFA 算法

在实现文字过滤的算法中,DFA是比较好的实现算法。

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

流程图示

下图展示了其状态的转换

image

在这幅图中大写字母(S、U、V、Q)都是状态,小写字母a、b为动作。

通过上图我们可以看到如下关系

            a b b
S -----> U S -----> V U -----> V

ps: 说到有穷状态机,又让我想起了正则表达式。感觉冥冥之中,这些东西还是相通的。

在实现敏感词过滤的算法中,我们必须要减少运算,而DFA在DFA算法中几乎没有什么计算,有的只是状态的转换。

开源地址

为了便于大家学习,项目开源地址如下,欢迎 fork+star 鼓励一下老马~

sensitive-word

java 实现

词库准备

敏感词库就是一行行敏感词,比如【黄色】

虽然说这个词可能是中性的,但是我们只是举一个比较和谐的例子。

流程梳理

我们根据上面的图,可以将状态图整理如下:

image

同时这里没有状态转换,没有动作,有的只是Query(查找)。

我们可以认为,通过S query U、V,通过U query V、P,通过V query U P。

通过这样的转变我们可以将状态的转换转变为使用Java集合的查找。

例子

诚然,加入在我们的敏感词库中存在如下几个敏感词:日本人、日本鬼子。

那么我需要构建成一个什么样的结构呢?

首先:

query 日 ---> {本}、query 本 --->{人、鬼子}、query 人 --->{null}、query 鬼 ---> {子}

形如下结构:

image

这样我们就将我们的敏感词库构建成了一个类似与一颗一颗的树,这样我们判断一个词是否为敏感词时就大大减少了检索的匹配范围。比如我们要判断日本人,根据第一个字我们就可以确认需要检索的是那棵树,然后再在这棵树中进行检索。

如何判断结束

但是如何来判断一个敏感词已经结束了呢?

利用标识位来判断。

所以对于这个关键是如何来构建一棵棵这样的敏感词树。

具体流程

下面我已Java中的HashMap为例来实现DFA算法。

具体过程如下:

日本人,日本鬼子为例

1、在hashMap中查询“日”看其是否在hashMap中存在,如果不存在,则证明已“日”开头的敏感词还不存在,则我们直接构建这样的一棵树。跳至3。

2、如果在hashMap中查找到了,表明存在以“日”开头的敏感词,设置hashMap = hashMap.get(“日”),跳至1,依次匹配“本”、“人”。

3、判断该字是否为该词中的最后一个字。若是表示敏感词结束,设置标志位 isEnd = 1,否则设置标志位 isEnd = 0;

image

java 程序实现

DFA 树的初始化

@SuppressWarnings("unchecked")
word">public word">void initWordMap(Collection<String> collection) {
    // 避免重复加载
    word">if (MapUtil.isNotEmpty(innerWordMap)) {
        word">return;
    }
    word">long startTime = System.currentTimeMillis();
    // 避免扩容带来的消耗
    innerWordMap = word">new HashMap(collection.size());
    word">for (String key : collection) {
        word">if (StringUtil.isEmpty(key)) {
            word">continue;
        }
        // 用来按照相应的格式保存敏感词库数据
        word">char[] chars = key.toCharArray();
        word">final word">int size = chars.length;
        // 每一个新词的循环,直接将结果设置为当前 map,所有变化都会体现在结果的 map 中
        Map currentMap = innerWordMap;
        word">for (word">int i = 0; i < size; i++) {
            // 截取敏感词当中的字,在敏感词库中字为HashMap对象的Key键值
            word">char charKey = chars[i];
            // 如果集合存在
            Object wordMap = currentMap.get(charKey);
            // 如果集合存在
            word">if (ObjectUtil.isNotNull(wordMap)) {
                // 直接将获取到的 map 当前当前 map 进行继续的操作
                currentMap = (Map) wordMap;
            } word">else {
                //不存在则,则构建一个新的map,同时将isEnd设置为0,因为他不是最后一
                Map<String, Boolean> newWordMap = word">new HashMap<>(8);
                newWordMap.put(AppConst.IS_END, false);
                // 将新的节点放入当前 map 中
                currentMap.put(charKey, newWordMap);
                // 将新节点设置为当前节点,方便下一次节点的循环。
                currentMap = newWordMap;
            }
            // 判断是否为最后一个,添加是否结束的标识。
            word">if (i == size - 1) {
                currentMap.put(AppConst.IS_END, true);
            }
        }
    }
    word">long endTime = System.currentTimeMillis();
    System.out.println("Init sensitive word map end! Cost time: " + (endTime - startTime) + "ms");
}

DFA 树的使用

/**
 * 检查敏感词
 * <p>
 * (1)如果未命中敏感词,直接返回 0
 * (2)命中敏感词,则返回敏感词的长度。
 *
 * ps: 这里结果进行优化,
 * 1. 是否包含敏感词。
 * 2. 敏感词的长度
 * 3. 正常走过字段的长度(便于后期替换优化,避免不必要的循环重复)
 *
 * @param txt           文本信息
 * @param beginIndex    开始下标
 * @param validModeEnum 验证模式
 * @param context 执行上下文
 * @return 敏感词对应的长度
 * @since 0.0.1
 */
word">private word">int checkSensitiveWord(word">final String txt, word">final word">int beginIndex,
                               word">final ValidModeEnum validModeEnum,
                               word">final IWordContext context) {
    Map nowMap = innerWordMap;
    // 记录敏感词的长度
    word">int lengthCount = 0;
    word">int actualLength = 0;
    word">for (word">int i = beginIndex; i < txt.length(); i++) {
        word">char c = txt.charAt(i);
        word">char charKey = getActualChar(c, context);
        // 判断该字是否存在于敏感词库中
        // 并且将 nowMap 替换为新的 map,进入下一层的循环。
        nowMap = (Map) nowMap.get(charKey);
        word">if (ObjectUtil.isNotNull(nowMap)) {
            lengthCount++;
            // 判断是否是敏感词的结尾字,如果是结尾字则判断是否继续检测
            word">boolean isEnd = (word">boolean) nowMap.get(AppConst.IS_END);
            word">if (isEnd) {
                // 只在匹配到结束的时候才记录长度,避免不完全匹配导致的问题。
                // eg: 敏感词 敏感词xxx
                // 如果是 【敏感词x】也会被匹配。
                actualLength = lengthCount;
                // 这里确实需要一种验证模式,主要是为了最大匹配从而达到最佳匹配的效果。
                word">if (ValidModeEnum.FAIL_FAST.equals(validModeEnum)) {
                    word">break;
                }
            }
        } word">else {
            // 直接跳出循环
            word">break;
        }
    }
    word">return actualLength;
}

DFA 算法的思考

虽说性能上没有任何问题。

但是 DFA 有一个缺点,那就是占用的内存和敏感词字典的大小成正比。

开源框架

参考 sensitive-word

参考资料

Java实现敏感词过滤

使用 DFA 实现文字过滤

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


http://www.niftyadmin.cn/n/5275952.html

相关文章

【lesson20】MySQL复合查询(1)基本查询回顾、多表查询和自连接

文章目录 基本查询回顾建表插入数据实例 多表查询建表插入数据实例 自连接建表插入数据实例 基本查询回顾 建表 插入数据 实例 查询工资高于500或岗位为MANAGER的雇员&#xff0c;同时还要满足他们的姓名首字母为大写的J 按照部门号升序而雇员的工资降序排序 使用年薪进行降…

【六大排序详解】开篇 :插入排序 与 希尔排序

插入排序 与 希尔排序 六大排序之二 插入排序 与 希尔排序1 排序1.1排序的概念 2 插入排序2.1 插入排序原理2.2 排序步骤2.3 代码实现 3 希尔排序3.1 希尔排序原理3.2 排序步骤3.3 代码实现 4 时间复杂度分析 Thanks♪(&#xff65;ω&#xff65;)&#xff89;下一篇文章见&am…

uniapp地图开发(APP,H5)

uniapp地图开发&#xff08;APP&#xff0c;H5&#xff09; 背景实现页面实现功能实现注意事项 尾巴 背景 最近项目中需要使用地图相关功能&#xff0c;需要用到聚合&#xff0c;marker拖拽&#xff0c;自定义marker显示内容&#xff0c;根据角色不同maker显示不同图标等功能。…

操作系统系列:关于终端Shell

操作系统系列&#xff1a;关于终端 Shell在Win32上创建一个新进程重定向输入和输出 Shell Unix命令处理器或者Shell都是进程&#xff0c;它获取用户键入的命令&#xff0c;fork出一个进程&#xff0c;子进程调用exec来执行用户的命令&#xff0c;父进程等待子进程执行结束。 这…

银行测试:第三方支付平台业务流,功能/性能/安全测试方法

1、第三方支付平台的功能和结构特点 在信用方面&#xff0c;第三方支付平台作为中介&#xff0c;在网上交易的商家和消费者之间作一个信用的中转&#xff0c;通过改造支付流程来约束双方的行为&#xff0c;从而在一定程度上缓解彼此对双方信用的猜疑&#xff0c;增加对网上购物…

文献速递:生成对抗网络医学影像中的应用——基于生成对抗网络的医学图像处理:系统综述

本周给大家分享文献的主题是生成对抗网络&#xff08;Generative adversarial networks, GANs&#xff09;在医学影像中的应用。文献的研究内容包括同模态影像生成、跨模态影像生成、GAN在分类和分割方面的应用等。生成对抗网络与其他方法相比展示出了优越的数据生成能力&#…

学习k8s

学习k8s 我为什么要用k8s 和其他部署方式的区别是什么? 传统部署方式 java --> package --> 放到服务器上 --> Tomcat 如果是同时进行写操作,会存在并发问题. 用户 --网络带宽–> 服务器 -->服务 同一个服务器上,多个服务: 网络资源的占用 内存的占用 cpu的占…

Linux 下 GCC 编译共享库控制导出函数的方法

通过一些实际项目的开发&#xff0c;发现这样一个现象&#xff0c;在 Windows 下可以通过指定 __declspec(dllexport) 定义来控制 DLL&#xff08;动态链接库&#xff09;中哪些函数可以导出&#xff0c;暴露给其他程序链接使用&#xff0c;哪些函数是 DLL 内部自己使用&#x…