DFA(Deterministic Finite Automaton,确定性有限自动机)是一种常用的算法模型,在Java中广泛应用于敏感词过滤 、字符串匹配、词法分析等场景。它的核心特点是:每个状态对于同一个输入字符,有且只有一个转移状态。
基本原理
DFA算法通过构建一个状态转移图(通常用嵌套Map或数组表示)来快速匹配关键词。相比于简单遍历匹配,时间复杂度为 O(n),n为待匹配文本长度,与关键词数量无关。
Java实现示例
1. 构建DFA节点模型
java
import java.util.*;
public class DFAFilter {
// 根节点
private Map<Character, Object> rootNode = new HashMap<>();
// 敏感词结束标识
private static final String END_FLAG = "isEnd";
/**
* 添加关键词到DFA中
* @param keyword 关键词
*/
public void addKeyword(String keyword) {
if (keyword == null || keyword.isEmpty()) return;
Map<Character, Object> currentMap = rootNode;
for (int i = 0; i < keyword.length(); i++) {
char c = keyword.charAt(i);
// 获取下一层节点
Map<Character, Object> nextMap =
(Map<Character, Object>) currentMap.get(c);
if (nextMap == null) {
nextMap = new HashMap<>();
currentMap.put(c, nextMap);
}
currentMap = nextMap;
// 最后一个字符标记结束
if (i == keyword.length() - 1) {
currentMap.put(END_FLAG, true);
}
}
}
/**
* 批量添加关键词
*/
public void addKeywords(List<String> keywords) {
for (String keyword : keywords) {
addKeyword(keyword);
}
}
}
2. 匹配敏感词
java
/**
* 检查文本是否包含敏感词,并返回第一个匹配的敏感词
*/
public String check(String text) {
if (text == null || text.isEmpty()) return null;
for (int i = 0; i < text.length(); i++) {
int matchCount = 0;
Map<Character, Object> currentMap = rootNode;
char c;
for (int j = i; j < text.length(); j++) {
c = text.charAt(j);
currentMap = (Map<Character, Object>) currentMap.get(c);
if (currentMap == null) {
break; // 匹配失败,重新从下一个字符开始
}
matchCount++;
// 检查是否匹配到完整关键词
if (currentMap.containsKey(END_FLAG)) {
return text.substring(i, i + matchCount);
}
}
}
return null;
}
/**
* 替换文本中的敏感词
* @param text 原始文本
* @param replaceChar 替换字符
* @return 替换后的文本
*/
public String replace(String text, char replaceChar) {
if (text == null || text.isEmpty()) return text;
StringBuilder result = new StringBuilder(text);
List<int[]> positions = new ArrayList<>();
for (int i = 0; i < text.length(); i++) {
int matchCount = 0;
Map<Character, Object> currentMap = rootNode;
char c;
for (int j = i; j < text.length(); j++) {
c = text.charAt(j);
currentMap = (Map<Character, Object>) currentMap.get(c);
if (currentMap == null) break;
matchCount++;
if (currentMap.containsKey(END_FLAG)) {
positions.add(new int[]{i, i + matchCount});
i = i + matchCount - 1; // 跳过已匹配部分
break;
}
}
}
// 执行替换
for (int[] pos : positions) {
for (int i = pos[0]; i < pos[1]; i++) {
result.setCharAt(i, replaceChar);
}
}
return result.toString();
}
3. 完整示例
java
public class DFATest {
public static void main(String[] args) {
DFAFilter filter = new DFAFilter();
// 添加敏感词
filter.addKeywords(Arrays.asList(
"色情", "暴力", "赌博", "毒品", "法轮功"
));
// 测试文本
String text = "这个网站包含色情和暴力内容,还有赌博信息。";
// 检查敏感词
String sensitive = filter.check(text);
System.out.println("检测到敏感词: " + sensitive);
// 替换敏感词
String filtered = filter.replace(text, '*');
System.out.println("过滤后: " + filtered);
}
}
输出:
检测到敏感词: 色情
过滤后: 这个网站包含**和**内容,还有**信息。
优化版本:支持跳过特殊字符
java
/**
* 增强版:支持忽略特殊字符(如空格、标点)
*/
public class AdvancedDFAFilter {
private Map<Character, Object> rootNode = new HashMap<>();
private static final String END_FLAG = "isEnd";
// 忽略字符集合(空格、标点符号等)
private Set<Character> ignoreChars = new HashSet<>(
Arrays.asList(' ', ',', '.', '!', '?', ';', ':', '、', ',', '。', '!', '?')
);
public void addKeyword(String keyword) {
Map<Character, Object> currentMap = rootNode;
for (char c : keyword.toCharArray()) {
currentMap = (Map<Character, Object>) currentMap.computeIfAbsent(
c, k -> new HashMap<>()
);
}
currentMap.put(END_FLAG, true);
}
public String check(String text) {
for (int i = 0; i < text.length(); i++) {
int matchCount = 0;
Map<Character, Object> currentMap = rootNode;
for (int j = i; j < text.length(); j++) {
char c = text.charAt(j);
// 跳过忽略字符
if (ignoreChars.contains(c)) {
continue;
}
currentMap = (Map<Character, Object>) currentMap.get(c);
if (currentMap == null) break;
matchCount++;
if (currentMap.containsKey(END_FLAG)) {
return text.substring(i, j + 1);
}
}
}
return null;
}
}
性能优化建议
-
使用数组代替HashMap :当字符集有限(如纯英文)时,可改用
Map<Character, Node>或Node[] children数组 -
内存优化 :使用
char[]和int[]手动实现紧凑存储 -
并行处理:大文本可分片并行匹配
-
缓存结果:对于重复文本缓存匹配结果
应用场景
-
✅ 敏感词过滤系统
-
✅ 垃圾邮件检测
-
✅ SQL注入检测
-
✅ 代码语法高亮
-
✅ 正则表达式引擎底层实现
DFA算法在Java中实现简单、性能优秀,尤其适合需要高性能字符串匹配的场景,相比之下比遍历关键词列表快很多。