| 方案 | 時間複雜度 | 空間 | 適用場景 |
|---|---|---|---|
| 遍歷敏感詞庫 + String.contains | O(N × M × L) | 低 | 詞庫 < 100、低頻 |
| DFA (確定性有限自動機) | O(L) | 中等 | 詞庫 10k+、高頻、在線過濾 |
| AC 自動機 (Aho-Corasick) | O(L + 匹配數) | 高 | 多模式匹配、全文檢索 |
| 正則表達式 | 不可控 | 低 | 簡單模式、非海量 |
將敏感詞集合構建為前綴樹,每個節點為字符,標記 isEnd 標識詞尾。檢測時單次遍歷文本,狀態機跳轉,命中即輸出。
// 敏感詞庫:Map<Character, Node> 遞歸嵌套
// Node 結構:{ children: Map<Char, Node>, isEnd: boolean }
Map<Object, Object> sensitiveWordMap = new HashMap<>();
public class SensitiveWordInit {
public Map<Object, Object> initKeyWord(List<String> words) {
Map<Object, Object> root = new HashMap<>();
for (String word : words) {
Map<Object, Object> node = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
@SuppressWarnings("unchecked")
Map<Object, Object> next = (Map<Object, Object>) node.get(c);
if (next == null) {
next = new HashMap<>();
next.put("isEnd", false);
node.put(c, next);
}
node = next;
if (i == word.length() - 1) node.put("isEnd", true);
}
}
return root;
}
}
public class SensitiveWordEngine {
private static Map<Object, Object> WORD_MAP;
public static final int MIN_MATCH = 1; // 最短匹配
public static final int MAX_MATCH = 2; // 最長匹配
public static void setWordMap(Map<Object, Object> map) { WORD_MAP = map; }
/** 檢測文本中是否包含敏感詞 */
public static boolean contains(String text) {
for (int i = 0; i < text.length(); i++) {
if (check(text, i, MIN_MATCH) > 0) return true;
}
return false;
}
/** 獲取所有敏感詞(去重) */
public static Set<String> getWords(String text, int matchType) {
Set<String> result = new HashSet<>();
for (int i = 0; i < text.length(); i++) {
int len = check(text, i, matchType);
if (len > 0) {
result.add(text.substring(i, i + len));
i += len - 1; // 跳過已匹配部分
}
}
return result;
}
/** 替換敏感詞為指定字符 */
public static String replace(String text, int matchType, String replacement) {
StringBuilder sb = new StringBuilder(text);
Set<String> words = getWords(text, matchType);
for (String w : words) {
String repl = replacement.repeat(w.length());
int idx = -1;
while ((idx = sb.indexOf(w, idx + 1)) != -1) {
sb.replace(idx, idx + w.length(), repl);
}
}
return sb.toString();
}
/** 核心檢測邏輯:從 text[begin] 開始在字典樹中行進 */
private static int check(String text, int begin, int matchType) {
Map<Object, Object> node = WORD_MAP;
int matched = 0;
boolean isEnd = false;
for (int i = begin; i < text.length(); i++) {
char c = text.charAt(i);
@SuppressWarnings("unchecked")
Map<Object, Object> next = (Map<Object, Object>) node.get(c);
if (next == null) break;
matched++;
node = next;
if (Boolean.TRUE.equals(node.get("isEnd"))) {
isEnd = true;
if (matchType == MIN_MATCH) break; // 最短匹配即返回
}
}
return isEnd ? matched : 0;
}
}
@Service
public class SensitiveWordService {
@Resource private SensitiveWordDao dao;
public Set<String> filter(String text) {
// 1. 加載詞庫(建議啟動時緩存,定時刷新)
List<String> words = dao.loadAllWords();
Map<Object, Object> map = new SensitiveWordInit().initKeyWord(words);
SensitiveWordEngine.setWordMap(map);
// 2. 檢測
return SensitiveWordEngine.getWords(text, SensitiveWordEngine.MAX_MATCH);
}
}
@RestController
@RequestMapping("/api/sensitive")
public class SensitiveWordController {
@Resource private SensitiveWordService service;
@GetMapping("/check")
public R check(@RequestParam String text) {
Set<String> words = service.filter(text);
return R.ok().put("words", words).put("hasSensitive", !words.isEmpty());
}
}
ReadWriteLock 保護 WORD_MAP,定時任務異步重建新 Map 原子替換StringBuildermmap 加載,毫秒級初始化@Test
public void testEngine() {
List<String> words = Arrays.asList("敏感詞", "測試", "關鍵字");
Map<Object, Object> map = new SensitiveWordInit().initKeyWord(words);
SensitiveWordEngine.setWordMap(map);
String text = "這是一個測試文本,包含敏感詞和關鍵字。";
assertTrue(SensitiveWordEngine.contains(text));
assertEquals(Set.of("測試", "敏感詞", "關鍵字"),
SensitiveWordEngine.getWords(text, SensitiveWordEngine.MAX_MATCH));
assertEquals("這是一個**文本,包含****和****。",
SensitiveWordEngine.replace(text, SensitiveWordEngine.MAX_MATCH, "*"));
}