Java實(shí)現(xiàn)一個(gè)敏感詞過(guò)濾有哪些方法以及怎么優(yōu)化詳解
敏感詞過(guò)濾是非常常見(jiàn)的一種手段,避免出現(xiàn)一些違規(guī)詞匯。
Java實(shí)現(xiàn)敏感詞過(guò)濾的完整方案與優(yōu)化策略
敏感詞過(guò)濾是內(nèi)容安全的重要組成部分,以下是Java中實(shí)現(xiàn)敏感詞過(guò)濾的多種方法及其優(yōu)化方案。
一、基礎(chǔ)實(shí)現(xiàn)方法
1. 簡(jiǎn)單字符串匹配(適合小規(guī)模場(chǎng)景)
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):時(shí)間復(fù)雜度O(n*m),性能差,無(wú)法處理變形詞,拼音等擴(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)建時(shí)間長(zhǎng),敏感詞多時(shí)性能下降明顯。敏感詞有些場(chǎng)景還是可以考慮的,可以做一個(gè)分片處理。
二、高效實(shí)現(xiàn)方案
1. Trie樹(shù)(前綴樹(shù))實(shí)現(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樹(shù)
public void addWord(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
node = node.getChildren().computeIfAbsent(c, k -> new TrieNode());
}
node.setEnd(true);
}
// 過(guò)濾方法
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();
}
}其實(shí)也就是一種樹(shù)形有向圖(無(wú)環(huán))結(jié)構(gòu)。是DFA的一種特例(樹(shù)形結(jié)構(gòu),無(wú)失敗轉(zhuǎn)移)。
優(yōu)點(diǎn):時(shí)間復(fù)雜度O(n),適合大規(guī)模敏感詞庫(kù)
前綴樹(shù)的優(yōu)點(diǎn)是,插入和查詢效率高,特別是在敏感詞有共同前綴的情況下(如ab、abc、abcd)。而且他的空間效率較高,因?yàn)槭枪蚕砉睬熬Y的。
但是他也有缺點(diǎn),一方面是構(gòu)建樹(shù)的初期成本較高。另外對(duì)于沒(méi)有共同前綴的敏感詞,效率提升不明顯。
所以,前綴樹(shù)適合做高效的字典查找、根據(jù)前綴自動(dòng)補(bǔ)全、利用前綴匹配進(jìn)行快速路由等場(chǎng)景。
2. DFA(確定性有限自動(dòng)機(jī))算法
DFA是Deterministic Finite Automaton的縮寫(xiě),翻譯過(guò)來(lái)叫確定有限自動(dòng)機(jī),DFA算法是一種高效的文本匹配算法,特別適合于敏感詞過(guò)濾。
DFA由一組狀態(tài)組成,以及在這些狀態(tài)之間的轉(zhuǎn)換,這些轉(zhuǎn)換由輸入字符串驅(qū)動(dòng)。每個(gè)狀態(tài)都知道下一個(gè)字符的到來(lái)應(yīng)該轉(zhuǎn)移到哪個(gè)狀態(tài)。如果輸入字符串結(jié)束時(shí),DFA處于接受狀態(tài),則輸入字符串被認(rèn)為是匹配的。
其實(shí)就是一種一般有向圖(可能含環(huán),如自環(huán))結(jié)構(gòu),滿足一條路徑則算匹配成功,就算一個(gè)敏感詞了。
有三個(gè)參數(shù)組成
節(jié)點(diǎn)(States):表示自動(dòng)機(jī)的狀態(tài),包括:
初始狀態(tài)(起點(diǎn))
中間狀態(tài)
終止?fàn)顟B(tài)(敏感詞匹配成功的狀態(tài))
邊(Transitions):表示狀態(tài)之間的轉(zhuǎn)移條件,每個(gè)邊對(duì)應(yīng)一個(gè)輸入字符(如字母、漢字)。
終止?fàn)顟B(tài):某些節(jié)點(diǎn)被標(biāo)記為終止?fàn)顟B(tài),代表從初始狀態(tài)到該狀態(tài)的路徑對(duì)應(yīng)一個(gè)完整的敏感詞。
具體過(guò)程就像下面這樣
輸入字符 c,檢查當(dāng)前狀態(tài)是否有 c 對(duì)應(yīng)的邊。
如果有,轉(zhuǎn)移到下一個(gè)狀態(tài);如果沒(méi)有,匹配失敗。
如果最終停在終止?fàn)顟B(tài),則輸入文本包含敏感詞。
否則,不包含。
public class DFAFilter {
private Map<String, Object> sensitiveWordMap = new HashMap<>();
// 構(gòu)建敏感詞庫(kù)
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");
}
}
}
}
// 過(guò)濾方法
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)存儲(chǔ),減少內(nèi)存占用。
共享前綴:DFA合并相同前綴的狀態(tài)(如
"敏感詞"和"敏感內(nèi)容"共享"敏感"路徑)。
匹配加速:
AC自動(dòng)機(jī):在DFA基礎(chǔ)上添加失敗指針,支持多模式匹配(類(lèi)似KMP算法)。
批處理:對(duì)長(zhǎng)文本分塊并行檢測(cè)。
工程實(shí)踐:
熱更新:動(dòng)態(tài)加載敏感詞庫(kù),無(wú)需重啟服務(wù)。
多級(jí)過(guò)濾:先布隆過(guò)濾器快速排除無(wú)敏感詞文本,再走DFA精確匹配。
給大家推薦一個(gè)基于 DFA 算法實(shí)現(xiàn)的高性能 java 敏感詞過(guò)濾工具框架——sensitive-word
三、高級(jí)優(yōu)化方案
1. 多模式匹配算法優(yōu)化
AC自動(dòng)機(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):一次掃描匹配所有模式串,時(shí)間復(fù)雜度O(n)
2. 基于布隆過(guò)濾器的預(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)行精確匹配
四、工程化實(shí)踐方案
1. 敏感詞庫(kù)動(dòng)態(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ù)庫(kù)或文件加載敏感詞
Set<String> words = loadFromDB();
// 構(gòu)建DFA結(jié)構(gòu)
this.wordMap = buildDFA(words);
}
}2. 分布式敏感詞過(guò)濾
public class DistributedFilter {
private RedisTemplate<String, String> redisTemplate;
public boolean isSensitive(String text) {
// 使用Redis的Set結(jié)構(gòu)存儲(chǔ)敏感詞
return redisTemplate.opsForSet().isMember("sensitive:words", text);
}
public String filter(String text) {
// 調(diào)用分布式過(guò)濾服務(wù)
return restTemplate.postForObject("http://filter-service/filter", text, String.class);
}
}3:也可以考慮使用ElasticSearch做搜索引擎
為什么可以使用ES可以看看
為什么用ElasticSearch?和傳統(tǒng)數(shù)據(jù)庫(kù)MySQL與什么區(qū)別?
總的來(lái)說(shuō)就是ES有強(qiáng)大的文本分析和查詢能力來(lái)實(shí)現(xiàn)。以下是詳細(xì)實(shí)現(xiàn)過(guò)程和方案:
具體實(shí)現(xiàn)方案
方案1:索引時(shí)敏感詞標(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)容 } } } } } }檢測(cè)敏感詞:
GET /sensitive_content_index/_analyze { "analyzer": "sensitive_filter_analyzer", "text": "這是一段包含敏感詞1的文本" }輸出:敏感詞會(huì)被過(guò)濾掉,只返回普通詞項(xiàng)
寫(xiě)入時(shí)自動(dòng)標(biāo)記:
POST /sensitive_content_index/_doc { "content": "這是需要檢測(cè)的文本", "has_sensitive": false // 由pipeline更新 }使用Ingest Pipeline自動(dòng)檢測(cè):
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:查詢時(shí)敏感詞過(guò)濾
使用Term查詢檢測(cè):
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)練敏感詞分類(lèi)模型:
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)化技巧
敏感詞庫(kù)存儲(chǔ)優(yōu)化:
使用ES的Synonyms Token Filter管理同義詞/變體詞
將敏感詞庫(kù)存儲(chǔ)在單獨(dú)索引中,定期更新
緩存加速:
PUT /sensitive_words_cache { "mappings": { "properties": { "word": { "type": "keyword" } } } }分布式檢測(cè):
對(duì)大型文檔分片處理
使用
_search_shardsAPI并行檢測(cè)
五、性能優(yōu)化技巧
內(nèi)存優(yōu)化:
使用基本類(lèi)型替代包裝類(lèi)
壓縮Trie樹(shù)結(jié)構(gòu)(Ternary Search Tree)
對(duì)象復(fù)用減少GC壓力
算法優(yōu)化:
對(duì)短文本使用快速失敗策略
實(shí)現(xiàn)多級(jí)過(guò)濾(先粗篩后精篩),
并行化處理(Fork/Join框架)
預(yù)處理優(yōu)化:
文本歸一化(全角轉(zhuǎn)半角,繁體轉(zhuǎn)簡(jiǎn)體)
拼音轉(zhuǎn)換處理(如"taobao"->"淘寶")
近音詞/形近詞處理
緩存優(yōu)化:
緩存常見(jiàn)文本的過(guò)濾結(jié)果
使用Caffeine實(shí)現(xiàn)本地緩存
布隆過(guò)濾器預(yù)判
六、完整生產(chǎn)級(jí)實(shí)現(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);
}
// 其他輔助方法...
}七、評(píng)估指標(biāo)
性能指標(biāo):
吞吐量(QPS)
平均延遲(ms)
99線延遲(ms)
效果指標(biāo):
召回率(漏判率)
準(zhǔn)確率(誤判率)
覆蓋度(變形詞識(shí)別率)
資源消耗:
內(nèi)存占用
CPU使用率
網(wǎng)絡(luò)IO(分布式場(chǎng)景)
八、擴(kuò)展思考
中文分詞集成:結(jié)合IK Analyzer等分詞工具處理更復(fù)雜的語(yǔ)義
機(jī)器學(xué)習(xí)模型:使用NLP模型識(shí)別變體、諧音、拆字等高級(jí)變種
圖片/語(yǔ)音過(guò)濾:擴(kuò)展多媒體內(nèi)容過(guò)濾能力
多語(yǔ)言支持:處理Unicode混淆和國(guó)際化敏感詞
對(duì)于大多數(shù)Java應(yīng)用,Trie樹(shù)或DFA算法配合定期更新的詞庫(kù)已經(jīng)能夠滿足需求。
總結(jié)
到此這篇關(guān)于Java實(shí)現(xiàn)一個(gè)敏感詞過(guò)濾有哪些方法以及怎么優(yōu)化的文章就介紹到這了,更多相關(guān)Java實(shí)現(xiàn)敏感詞過(guò)濾內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
@Autowired注解注入的xxxMapper報(bào)錯(cuò)問(wèn)題及解決
這篇文章主要介紹了@Autowired注解注入的xxxMapper報(bào)錯(cuò)問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
HttpServletRequest對(duì)象方法的用法小結(jié)
HttpServletRequest對(duì)象代表客戶端的請(qǐng)求,當(dāng)客戶端通過(guò)HTTP協(xié)議訪問(wèn)服務(wù)器時(shí),HTTP請(qǐng)求頭中的所有信息都封裝在這個(gè)對(duì)象中,開(kāi)發(fā)人員通過(guò)這個(gè)對(duì)象的相關(guān)方法,即可以獲得客戶的這些信息2017-03-03
Kotlin中使用Java數(shù)據(jù)類(lèi)時(shí)引發(fā)的Bug解決方式
這篇文章主要介紹了Kotlin中使用Java數(shù)據(jù)類(lèi)時(shí)引發(fā)的一個(gè)Bug,本文給大家分享問(wèn)題解決方式,感興趣的朋友跟隨小編一起看看吧2023-09-09
基于OpenID?Connect及Token?Relay實(shí)現(xiàn)Spring?Cloud?Gateway
這篇文章主要介紹了基于OpenID?Connect及Token?Relay實(shí)現(xiàn)Spring?Cloud?Gateway,Spring?Cloud?Gateway旨在提供一種簡(jiǎn)單而有效的方式來(lái)路由到API,并為API提供跨領(lǐng)域的關(guān)注點(diǎn),如:安全性、監(jiān)控/指標(biāo)和彈性2022-06-06
Java并發(fā)Futures和Callables類(lèi)實(shí)例詳解
Callable對(duì)象返回Future對(duì)象,該對(duì)象提供監(jiān)視線程執(zhí)行的任務(wù)進(jìn)度的方法, Future對(duì)象可用于檢查Callable的狀態(tài),然后線程完成后從Callable中檢索結(jié)果,這篇文章給大家介紹Java并發(fā)Futures和Callables類(lèi)的相關(guān)知識(shí),感興趣的朋友一起看看吧2024-05-05
Spring + mybatis + mysql使用事物的幾種方法總結(jié)
這篇文章主要給大家總結(jié)介紹了關(guān)于Spring + mybatis + mysql使用事物的幾種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05

