Java 实现 DFA 算法(敏感词过滤场景)

DFA(确定性有限自动机) 是一种高效字符串匹配算法,核心是将敏感词构造成 Trie 前缀树,匹配时沿状态树单次遍历文本(O (n) 复杂度),不受词库规模影响。
 

 

一、DFA 核心原理

 
DFA 用五元组定义:M=(Q, Σ, δ, q₀, F)
 
  • Q:有限状态集(树节点)
  • Σ:输入字符集(文本字符)
  • δ:状态转移(节点子节点映射)
  • q₀:根节点(初始状态)
  • F:终止状态(敏感词结尾标记)
 
构建:将敏感词逐字符插入树,共享前缀、结尾标记 isEnd
 
匹配:遍历文本,沿树转移;到终止态即命中敏感词
 

 

二、Java 完整实现(推荐)

 

1. 节点类(TrieNode)

import java.util.HashMap;
import java.util.Map;

/**
 * DFA 节点(Trie树节点)
 */
public class DfaNode {
    // 子节点:字符 -> 下一个节点
    private final Map<Character, DfaNode> children = new HashMap<>();
    // 是否为敏感词结尾
    private boolean isEnd = false;

    // 添加子节点
    public void addChild(char c, DfaNode node) {
        children.put(c, node);
    }

    // 获取子节点
    public DfaNode getChild(char c) {
        return children.get(c);
    }

    // 判断是否包含子节点
    public boolean containsChild(char c) {
        return children.containsKey(c);
    }

    // getter & setter
    public boolean isEnd() {
        return isEnd;
    }

    public void setEnd(boolean end) {
        isEnd = end;
    }
}

2. DFA 过滤器核心类

import java.util.Set;

/**
 * DFA 敏感词过滤器
 */
public class DfaSensitiveFilter {
    // 根节点
    private final DfaNode root = new DfaNode();

    /**
     * 初始化:批量添加敏感词,构建DFA树
     */
    public void init(Set<String> sensitiveWords) {
        if (sensitiveWords == null || sensitiveWords.isEmpty()) {
            return;
        }
        for (String word : sensitiveWords) {
            addWord(word);
        }
    }

    /**
     * 单个添加敏感词到DFA树
     */
    public void addWord(String word) {
        if (word == null || word.trim().isEmpty()) {
            return;
        }
        DfaNode current = root;
        char[] chars = word.toCharArray();
        for (char c : chars) {
            // 不存在则新建节点
            if (!current.containsChild(c)) {
                current.addChild(c, new DfaNode());
            }
            // 进入下一层
            current = current.getChild(c);
        }
        // 标记词尾
        current.setEnd(true);
    }

    /**
     * 检查文本是否包含敏感词(存在返回true)
     */
    public boolean containsSensitive(String text) {
        if (text == null || text.trim().isEmpty()) {
            return false;
        }
        char[] chars = text.toCharArray();
        int len = chars.length;
        for (int i = 0; i < len; i++) {
            DfaNode current = root;
            // 从i开始逐字符匹配
            for (int j = i; j < len; j++) {
                current = current.getChild(chars[j]);
                if (current == null) {
                    break; // 不匹配,退出内层循环
                }
                if (current.isEnd()) {
                    return true; // 命中敏感词
                }
            }
        }
        return false;
    }

    /**
     * 过滤敏感词(替换为*)
     */
    public String filter(String text) {
        if (text == null || text.trim().isEmpty()) {
            return text;
        }
        char[] chars = text.toCharArray();
        int len = chars.length;
        StringBuilder result = new StringBuilder();
        int i = 0;
        while (i < len) {
            DfaNode current = root;
            int matchEnd = -1;
            // 寻找最长匹配
            for (int j = i; j < len; j++) {
                current = current.getChild(chars[j]);
                if (current == null) {
                    break;
                }
                if (current.isEnd()) {
                    matchEnd = j; // 记录匹配结束位置
                }
            }
            if (matchEnd != -1) {
                // 替换为*
                for (int k = i; k <= matchEnd; k++) {
                    result.append('*');
                }
                i = matchEnd + 1;
            } else {
                // 保留原字符
                result.append(chars[i]);
                i++;
            }
        }
        return result.toString();
    }
}

3. 测试类

import java.util.HashSet;
import java.util.Set;

public class DfaTest {
    public static void main(String[] args) {
        // 1. 初始化敏感词库
        Set<String> words = new HashSet<>();
        words.add("赌博");
        words.add("毒品");
        words.add("诈骗");
        words.add("色情");

        // 2. 构建DFA过滤器
        DfaSensitiveFilter filter = new DfaSensitiveFilter();
        filter.init(words);

        // 3. 测试检测
        String text1 = "远离赌博毒品,拒绝诈骗色情";
        System.out.println("是否包含敏感词:" + filter.containsSensitive(text1)); // true

        // 4. 测试过滤
        String text2 = "这里有赌博信息和色情内容,小心诈骗";
        String filtered = filter.filter(text2);
        System.out.println("过滤前:" + text2);
        System.out.println("过滤后:" + filtered);
        // 输出:这里有**信息和**内容,小心**
    String text3 = "我是骗子王,请勿打扰";
    String filtered2 = filter.filter(text3);
    System.out.println("过滤前:" + text3); // 过滤前:我是骗子王,请勿打扰
    System.out.println("过滤后:" + filtered2); // 过滤后:我是***,请勿打扰
    }
}

四、核心特点与优化

1. 优点

  • 高效:匹配 O(n)(n = 文本长度)
  • 稳定:速度不受敏感词数量影响
  • 共享前缀:节省内存、构建快

2. 常用优化

  • 大小写忽略:添加 / 匹配时统一转小写
  • 特殊字符过滤:跳过空格、标点、特殊符号
  • 最长匹配:优先匹配长敏感词(如 “广告词” 优先于 “广告”)
  • 并发安全:初始化用双重检查锁,更新时重建树
  • 动态刷新:支持运行时增删敏感词
 

 

五、适用场景

  • 内容审核、评论过滤、聊天监控
  • 日志关键词扫描、规则匹配
  • 词法分析、关键字检索、文本预处理
posted @ 2026-04-03 14:41  都是城市惹的祸  阅读(0)  评论(0)    收藏  举报