Java實現(xiàn)一個敏感詞過濾有哪些方法以及怎么優(yōu)化詳解
敏感詞過濾是非常常見的一種手段,避免出現(xiàn)一些違規(guī)詞匯。
Java實現(xiàn)敏感詞過濾的完整方案與優(yōu)化策略
敏感詞過濾是內(nèi)容安全的重要組成部分,以下是Java中實現(xiàn)敏感詞過濾的多種方法及其優(yōu)化方案。
一、基礎(chǔ)實現(xiàn)方法
1. 簡單字符串匹配(適合小規(guī)模場景)
public class SimpleFilter {
private static final Set<String> sensitiveWords = new HashSet<>(Arrays.asList("敏感詞1", "敏感詞2"));
public static String filter(String text) {
for (String word : sensitiveWords) {
if (text.contains(word)) {
text = text.replace(word, "***");
}
}
return text;
}
}缺點(diǎn):時間復(fù)雜度O(n*m),性能差,無法處理變形詞,拼音等擴(kuò)展功能。
2. 正則表達(dá)式匹配
public class RegexFilter {
private static final String pattern = "敏感詞1|敏感詞2|敏感詞3";
public static String filter(String text) {
return text.replaceAll(pattern, "***");
}
}缺點(diǎn):正則構(gòu)建時間長,敏感詞多時性能下降明顯。敏感詞有些場景還是可以考慮的,可以做一個分片處理。
二、高效實現(xiàn)方案
1. Trie樹(前綴樹)實現(xiàn)
class TrieNode {
private Map<Character, TrieNode> children = new HashMap<>();
private boolean isEnd;
// 添加子節(jié)點(diǎn)方法
// 查找子節(jié)點(diǎn)方法
// getter/setter
}
public class TrieFilter {
private TrieNode root = new TrieNode();
// 構(gòu)建Trie樹
public void addWord(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
node = node.getChildren().computeIfAbsent(c, k -> new TrieNode());
}
node.setEnd(true);
}
// 過濾方法
public String filter(String text) {
StringBuilder result = new StringBuilder();
TrieNode temp;
for (int i = 0; i < text.length(); i++) {
temp = root;
int j = i;
while (j < text.length() && temp.getChildren().containsKey(text.charAt(j))) {
temp = temp.getChildren().get(text.charAt(j));
j++;
if (temp.isEnd()) {
// 發(fā)現(xiàn)敏感詞,替換為*
result.append("*".repeat(j - i));
i = j - 1;
break;
}
}
if (i >= text.length()) break;
if (!temp.isEnd()) {
result.append(text.charAt(i));
}
}
return result.toString();
}
}其實也就是一種樹形有向圖(無環(huán))結(jié)構(gòu)。是DFA的一種特例(樹形結(jié)構(gòu),無失敗轉(zhuǎn)移)。
優(yōu)點(diǎn):時間復(fù)雜度O(n),適合大規(guī)模敏感詞庫
前綴樹的優(yōu)點(diǎn)是,插入和查詢效率高,特別是在敏感詞有共同前綴的情況下(如ab、abc、abcd)。而且他的空間效率較高,因為是共享公共前綴的。
但是他也有缺點(diǎn),一方面是構(gòu)建樹的初期成本較高。另外對于沒有共同前綴的敏感詞,效率提升不明顯。
所以,前綴樹適合做高效的字典查找、根據(jù)前綴自動補(bǔ)全、利用前綴匹配進(jìn)行快速路由等場景。
2. DFA(確定性有限自動機(jī))算法
DFA是Deterministic Finite Automaton的縮寫,翻譯過來叫確定有限自動機(jī),DFA算法是一種高效的文本匹配算法,特別適合于敏感詞過濾。
DFA由一組狀態(tài)組成,以及在這些狀態(tài)之間的轉(zhuǎn)換,這些轉(zhuǎn)換由輸入字符串驅(qū)動。每個狀態(tài)都知道下一個字符的到來應(yīng)該轉(zhuǎn)移到哪個狀態(tài)。如果輸入字符串結(jié)束時,DFA處于接受狀態(tài),則輸入字符串被認(rèn)為是匹配的。
其實就是一種一般有向圖(可能含環(huán),如自環(huán))結(jié)構(gòu),滿足一條路徑則算匹配成功,就算一個敏感詞了。
有三個參數(shù)組成
節(jié)點(diǎn)(States):表示自動機(jī)的狀態(tài),包括:
初始狀態(tài)(起點(diǎn))
中間狀態(tài)
終止?fàn)顟B(tài)(敏感詞匹配成功的狀態(tài))
邊(Transitions):表示狀態(tài)之間的轉(zhuǎn)移條件,每個邊對應(yīng)一個輸入字符(如字母、漢字)。
終止?fàn)顟B(tài):某些節(jié)點(diǎn)被標(biāo)記為終止?fàn)顟B(tài),代表從初始狀態(tài)到該狀態(tài)的路徑對應(yīng)一個完整的敏感詞。
具體過程就像下面這樣
輸入字符 c,檢查當(dāng)前狀態(tài)是否有 c 對應(yīng)的邊。
如果有,轉(zhuǎn)移到下一個狀態(tài);如果沒有,匹配失敗。
如果最終停在終止?fàn)顟B(tài),則輸入文本包含敏感詞。
否則,不包含。
public class DFAFilter {
private Map<String, Object> sensitiveWordMap = new HashMap<>();
// 構(gòu)建敏感詞庫
public void init(Set<String> words) {
for (String word : words) {
Map<String, Object> nowMap = sensitiveWordMap;
for (int i = 0; i < word.length(); i++) {
String key = String.valueOf(word.charAt(i));
Object tempMap = nowMap.get(key);
if (tempMap == null) {
Map<String, Object> newMap = new HashMap<>();
newMap.put("isEnd", "0");
nowMap.put(key, newMap);
nowMap = newMap;
} else {
nowMap = (Map<String, Object>) tempMap;
}
if (i == word.length() - 1) {
nowMap.put("isEnd", "1");
}
}
}
}
// 過濾方法
public String filter(String text) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
int length = checkWord(text, i);
if (length > 0) {
result.append("*".repeat(length));
i += length - 1;
} else {
result.append(text.charAt(i));
}
}
return result.toString();
}
private int checkWord(String text, int beginIndex) {
boolean flag = false;
int matchLength = 0;
Map<String, Object> tempMap = sensitiveWordMap;
for (int i = beginIndex; i < text.length(); i++) {
String word = String.valueOf(text.charAt(i));
tempMap = (Map<String, Object>) tempMap.get(word);
if (tempMap == null) break;
matchLength++;
if ("1".equals(tempMap.get("isEnd"))) {
flag = true;
break;
}
}
return flag ? matchLength : 0;
}
}內(nèi)存優(yōu)化:
雙數(shù)組Trie:壓縮狀態(tài)存儲,減少內(nèi)存占用。
共享前綴:DFA合并相同前綴的狀態(tài)(如
"敏感詞"和"敏感內(nèi)容"共享"敏感"路徑)。
匹配加速:
AC自動機(jī):在DFA基礎(chǔ)上添加失敗指針,支持多模式匹配(類似KMP算法)。
批處理:對長文本分塊并行檢測。
工程實踐:
熱更新:動態(tài)加載敏感詞庫,無需重啟服務(wù)。
多級過濾:先布隆過濾器快速排除無敏感詞文本,再走DFA精確匹配。
給大家推薦一個基于 DFA 算法實現(xiàn)的高性能 java 敏感詞過濾工具框架——sensitive-word
三、高級優(yōu)化方案
1. 多模式匹配算法優(yōu)化
AC自動機(jī)(Aho-Corasick算法)
public class ACFilter {
private ACTrie trie;
public void init(Set<String> words) {
trie = new ACTrie();
for (String word : words) {
trie.insert(word);
}
trie.buildFailureLinks();
}
public String filter(String text) {
Set<ACTrie.Match> matches = trie.parseText(text);
char[] chars = text.toCharArray();
for (ACTrie.Match match : matches) {
Arrays.fill(chars, match.getStart(), match.getEnd() + 1, '*');
}
return new String(chars);
}
}優(yōu)點(diǎn):一次掃描匹配所有模式串,時間復(fù)雜度O(n)
2. 基于布隆過濾器的預(yù)處理
public class BloomFilterPreprocessor {
private BloomFilter<String> bloomFilter;
private Set<String> exactMatchSet;
public void init(Set<String> words) {
bloomFilter = BloomFilter.create(Funnels.stringFunnel(), words.size(), 0.01);
exactMatchSet = new HashSet<>(words);
words.forEach(bloomFilter::put);
}
public boolean mightContain(String text) {
return bloomFilter.mightContain(text);
}
public boolean exactMatch(String text) {
return exactMatchSet.contains(text);
}
}用途:先快速判斷是否可能包含敏感詞,再進(jìn)行精確匹配
四、工程化實踐方案
1. 敏感詞庫動態(tài)加載
public class DynamicWordFilter {
private volatile Map<String, Object> wordMap;
private ScheduledExecutorService executor;
public void init() {
loadWords();
executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(this::loadWords, 1, 1, TimeUnit.HOURS);
}
private void loadWords() {
Map<String, Object> newMap = new HashMap<>();
// 從數(shù)據(jù)庫或文件加載敏感詞
Set<String> words = loadFromDB();
// 構(gòu)建DFA結(jié)構(gòu)
this.wordMap = buildDFA(words);
}
}2. 分布式敏感詞過濾
public class DistributedFilter {
private RedisTemplate<String, String> redisTemplate;
public boolean isSensitive(String text) {
// 使用Redis的Set結(jié)構(gòu)存儲敏感詞
return redisTemplate.opsForSet().isMember("sensitive:words", text);
}
public String filter(String text) {
// 調(diào)用分布式過濾服務(wù)
return restTemplate.postForObject("http://filter-service/filter", text, String.class);
}
}3:也可以考慮使用ElasticSearch做搜索引擎
為什么可以使用ES可以看看
為什么用ElasticSearch?和傳統(tǒng)數(shù)據(jù)庫MySQL與什么區(qū)別?
總的來說就是ES有強(qiáng)大的文本分析和查詢能力來實現(xiàn)。以下是詳細(xì)實現(xiàn)過程和方案:
具體實現(xiàn)方案
方案1:索引時敏感詞標(biāo)記(推薦)
步驟:
自定義分析器:
PUT /sensitive_content_index { "settings": { "analysis": { "analyzer": { "sensitive_filter_analyzer": { "type": "custom", "tokenizer": "standard", "filter": [ "lowercase", "sensitive_word_filter" ] } }, "filter": { "sensitive_word_filter": { "type": "stop", "stopwords": ["敏感詞1", "敏感詞2", "違法詞"] } } } }, "mappings": { "properties": { "content": { "type": "text", "analyzer": "sensitive_filter_analyzer", "fields": { "original": { "type": "keyword" // 保留原始內(nèi)容 } } } } } }檢測敏感詞:
GET /sensitive_content_index/_analyze { "analyzer": "sensitive_filter_analyzer", "text": "這是一段包含敏感詞1的文本" }輸出:敏感詞會被過濾掉,只返回普通詞項
寫入時自動標(biāo)記:
POST /sensitive_content_index/_doc { "content": "這是需要檢測的文本", "has_sensitive": false // 由pipeline更新 }使用Ingest Pipeline自動檢測:
PUT _ingest/pipeline/sensitive_check_pipeline { "processors": [ { "script": { "source": """ def sensitiveWords = ['敏感詞1', '違禁詞']; for (word in sensitiveWords) { if (ctx.content.contains(word)) { ctx.has_sensitive = true; ctx.sensitive_word = word; break; } } """ } } ] }
方案2:查詢時敏感詞過濾
使用Term查詢檢測:
GET /content_index/_search
{
"query": {
"bool": {
"must_not": [
{ "terms": { "content": ["敏感詞1", "違禁詞"] }}
]
}
}
}高亮顯示敏感詞:
GET /content_index/_search
{
"query": {
"match": { "content": "正常文本" }
},
"highlight": {
"fields": {
"content": {
"highlight_query": {
"terms": { "content": ["敏感詞1", "違禁詞"] }
}
}
}
}
}方案3:結(jié)合機(jī)器學(xué)習(xí)(ES 7.15+)
訓(xùn)練敏感詞分類模型:
PUT _ml/trained_models/sensitive_words_classifier { "input": {"field_names": ["text"]}, "inference_config": { "text_classification": { "vocabulary": ["敏感詞1", "變體詞", "拼音詞"] } } }部署推理處理器:
PUT _ingest/pipeline/ml_sensitive_detection { "processors": [ { "inference": { "model_id": "sensitive_words_classifier", "field_map": { "content": "text" } } } ] }
性能優(yōu)化技巧
敏感詞庫存儲優(yōu)化:
使用ES的Synonyms Token Filter管理同義詞/變體詞
將敏感詞庫存儲在單獨(dú)索引中,定期更新
緩存加速:
PUT /sensitive_words_cache { "mappings": { "properties": { "word": { "type": "keyword" } } } }分布式檢測:
對大型文檔分片處理
使用
_search_shardsAPI并行檢測
五、性能優(yōu)化技巧
內(nèi)存優(yōu)化:
使用基本類型替代包裝類
壓縮Trie樹結(jié)構(gòu)(Ternary Search Tree)
對象復(fù)用減少GC壓力
算法優(yōu)化:
對短文本使用快速失敗策略
實現(xiàn)多級過濾(先粗篩后精篩),
并行化處理(Fork/Join框架)
預(yù)處理優(yōu)化:
文本歸一化(全角轉(zhuǎn)半角,繁體轉(zhuǎn)簡體)
拼音轉(zhuǎn)換處理(如"taobao"->"淘寶")
近音詞/形近詞處理
緩存優(yōu)化:
緩存常見文本的過濾結(jié)果
使用Caffeine實現(xiàn)本地緩存
布隆過濾器預(yù)判
六、完整生產(chǎn)級實現(xiàn)示例
public class ProductionWordFilter implements InitializingBean {
private final TrieNode root = new TrieNode();
private final List<String> wordSources;
private final ScheduledExecutorService executor;
public ProductionWordFilter(List<String> wordSources) {
this.wordSources = wordSources;
this.executor = Executors.newSingleThreadScheduledExecutor();
}
@Override
public void afterPropertiesSet() {
reload();
executor.scheduleWithFixedDelay(this::reload, 1, 1, TimeUnit.HOURS);
}
public synchronized void reload() {
TrieNode newRoot = new TrieNode();
wordSources.stream()
.flatMap(source -> loadWords(source).stream())
.forEach(word -> addWord(newRoot, word));
this.root = newRoot;
}
public FilterResult filter(String text) {
StringBuilder result = new StringBuilder();
Set<String> foundWords = new HashSet<>();
int replacedCount = 0;
for (int i = 0; i < text.length(); ) {
MatchResult match = findNextMatch(text, i);
if (match != null) {
foundWords.add(match.getWord());
result.append("*".repeat(match.getLength()));
replacedCount++;
i = match.getEndIndex();
} else {
result.append(text.charAt(i));
i++;
}
}
return new FilterResult(result.toString(), foundWords, replacedCount);
}
// 其他輔助方法...
}七、評估指標(biāo)
性能指標(biāo):
吞吐量(QPS)
平均延遲(ms)
99線延遲(ms)
效果指標(biāo):
召回率(漏判率)
準(zhǔn)確率(誤判率)
覆蓋度(變形詞識別率)
資源消耗:
內(nèi)存占用
CPU使用率
網(wǎng)絡(luò)IO(分布式場景)
八、擴(kuò)展思考
中文分詞集成:結(jié)合IK Analyzer等分詞工具處理更復(fù)雜的語義
機(jī)器學(xué)習(xí)模型:使用NLP模型識別變體、諧音、拆字等高級變種
圖片/語音過濾:擴(kuò)展多媒體內(nèi)容過濾能力
多語言支持:處理Unicode混淆和國際化敏感詞
對于大多數(shù)Java應(yīng)用,Trie樹或DFA算法配合定期更新的詞庫已經(jīng)能夠滿足需求。
總結(jié)
到此這篇關(guān)于Java實現(xiàn)一個敏感詞過濾有哪些方法以及怎么優(yōu)化的文章就介紹到這了,更多相關(guān)Java實現(xiàn)敏感詞過濾內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaEE中關(guān)于ServletConfig的小結(jié)
ServletConfig是針對特定的Servlet的參數(shù)或?qū)傩浴ervletConfig是表示單獨(dú)的Servlet的配置和參數(shù),只是適用于特定的Servlet。從一個servlet被實例化后,對任何客戶端在任何時候訪問有效,但僅對本servlet有效,一個servlet的ServletConfig對象不能被另一個servlet訪問2014-10-10
Java簡單實現(xiàn)session保存到redis的方法示例
這篇文章主要介紹了Java簡單實現(xiàn)session保存到redis的方法,結(jié)合實例形式分析了Java將session存入redis緩存服務(wù)器的相關(guān)設(shè)置、實現(xiàn)技巧與操作注意事項,需要的朋友可以參考下2018-05-05
SpringSecurity在分布式環(huán)境下的使用流程分析
文章介紹了Spring?Security在分布式環(huán)境下的使用,包括單點(diǎn)登錄(SSO)的概念、流程圖以及JWT(JSON?Web?Token)的生成和校驗,通過使用JWT和RSA非對稱加密,可以實現(xiàn)安全的分布式認(rèn)證,感興趣的朋友一起看看吧2025-02-02
關(guān)于JSqlparser使用攻略(高效的SQL解析工具)
這篇文章主要介紹了關(guān)于JSqlparser使用攻略(高效的SQL解析工具),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11
java.io.UncheckedIOException: Cannot delete C
本文主要介紹了java.io.UncheckedIOException: Cannot delete C:\Users\guo\AppData\Local\Temp\tomcat.8081問題,具有一定的參考價值,感興趣的可以了解一下2024-05-05
SpringBoot整合Druid實現(xiàn)SQL監(jiān)控和數(shù)據(jù)庫密碼加密
Druid連接池是阿里巴巴開源的數(shù)據(jù)庫連接池項目,Druid連接池為監(jiān)控而生,內(nèi)置強(qiáng)大的監(jiān)控功能,監(jiān)控特性不影響性能,本文給大家介紹了SpringBoot整合Druid實現(xiàn)SQL監(jiān)控和數(shù)據(jù)庫密碼加密,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下2024-06-06
SpringBoot http請求注解@RestController原理解析
這篇文章主要介紹了SpringBoot http請求注解@RestController原理解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-01-01

