Java實(shí)現(xiàn)DFA算法對(duì)敏感詞、廣告詞過(guò)濾功能示例
一、前言
開(kāi)發(fā)中經(jīng)常要處理用戶一些文字的提交,所以涉及到了敏感詞過(guò)濾的功能,參考資料中DFA有窮狀態(tài)機(jī)算法的實(shí)現(xiàn),創(chuàng)建有向圖。完成了對(duì)敏感詞、廣告詞的過(guò)濾,而且效率較好,所以分享一下。
具體實(shí)現(xiàn):
1、匹配大小寫(xiě)過(guò)濾
2、匹配全角半角過(guò)濾
3、匹配過(guò)濾停頓詞過(guò)濾。
4、敏感詞重復(fù)詞過(guò)濾。
例如:
支持如下類(lèi)型類(lèi)型過(guò)濾檢測(cè):
fuck 全小寫(xiě)
FuCk 大小寫(xiě)
fuck全角半角
f!!!u&c ###k 停頓詞
fffuuuucccckkk 重復(fù)詞
敏感詞過(guò)濾的做法有很多,我簡(jiǎn)單描述我現(xiàn)在理解的幾種:
①查詢(xún)數(shù)據(jù)庫(kù)當(dāng)中的敏感詞,循環(huán)每一個(gè)敏感詞,然后去輸入的文本中從頭到尾搜索一遍,看是否存在此敏感詞,有則做相
應(yīng)的處理,這種方式講白了就是找到一個(gè)處理一個(gè)。
優(yōu)點(diǎn):so easy。用java代碼實(shí)現(xiàn)基本沒(méi)什么難度。
缺點(diǎn):這效率讓我心中奔過(guò)十萬(wàn)匹草泥馬,而且匹配的是不是有些蛋疼,如果是英文時(shí)你會(huì)發(fā)現(xiàn)一個(gè)很無(wú)語(yǔ)的事情,比如英文
a是敏感詞,那我如果是一篇英文文檔,那程序它妹的得處理多少次敏感詞?誰(shuí)能告訴我?
②傳說(shuō)中的DFA算法(有窮自動(dòng)機(jī)),也正是我要給大家分享的,畢竟感覺(jué)比較通用,算法的原理希望大家能夠自己去網(wǎng)上查查
資料,這里就不詳細(xì)說(shuō)明了。
優(yōu)點(diǎn):至少比上面那sb效率高點(diǎn)。
缺點(diǎn):對(duì)于學(xué)過(guò)算法的應(yīng)該不難,對(duì)于沒(méi)學(xué)過(guò)算法的用起來(lái)也不難,就是理解起來(lái)有點(diǎn)gg疼,匹配效率也不高,比較耗費(fèi)內(nèi)存,
敏感詞越多,內(nèi)存占用的就越大。
③第三種在這里要特別說(shuō)明一下,那就是你自己去寫(xiě)一個(gè)算法吧,或者在現(xiàn)有的算法的基礎(chǔ)上去優(yōu)化,這也是小Alan追求的至
高境界之一,如果哪位淫兄有自己的想法一定別忘了小Alan,可以加小Alan的QQ:810104041教小Alan兩招耍耍。
二、代碼實(shí)現(xiàn)
其目錄結(jié)構(gòu)如下:

其中resources資源目錄中:
stopwd.txt :停頓詞,匹配時(shí)間直接過(guò)濾。
wd.txt:敏感詞庫(kù)。
1、WordFilter敏感詞過(guò)濾類(lèi)
package org.andy.sensitivewdfilter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.andy.sensitivewdfilter.util.BCConvert;
/**
* 創(chuàng)建時(shí)間:2016年8月30日 下午3:01:12
*
* 思路: 創(chuàng)建一個(gè)FilterSet,枚舉了0~65535的所有char是否是某個(gè)敏感詞開(kāi)頭的狀態(tài)
*
* 判斷是否是 敏感詞開(kāi)頭 | | 是 不是 獲取頭節(jié)點(diǎn) OK--下一個(gè)字 然后逐級(jí)遍歷,DFA算法
*
* @author andy
* @version 2.2
*/
public class WordFilter {
private static final FilterSet set = new FilterSet(); // 存儲(chǔ)首字
private static final Map<Integer, WordNode> nodes = new HashMap<Integer, WordNode>(1024, 1); // 存儲(chǔ)節(jié)點(diǎn)
private static final Set<Integer> stopwdSet = new HashSet<>(); // 停頓詞
private static final char SIGN = '*'; // 敏感詞過(guò)濾替換
static {
try {
long a = System.nanoTime();
init();
a = System.nanoTime() - a;
System.out.println("加載時(shí)間 : " + a + "ns");
System.out.println("加載時(shí)間 : " + a / 1000000 + "ms");
} catch (Exception e) {
throw new RuntimeException("初始化過(guò)濾器失敗");
}
}
private static void init() {
// 獲取敏感詞
addSensitiveWord(readWordFromFile("wd.txt"));
addStopWord(readWordFromFile("stopwd.txt"));
}
/**
* 增加敏感詞
* @param path
* @return
*/
private static List<String> readWordFromFile(String path) {
List<String> words;
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(WordFilter.class.getClassLoader().getResourceAsStream(path)));
words = new ArrayList<String>(1200);
for (String buf = ""; (buf = br.readLine()) != null;) {
if (buf == null || buf.trim().equals(""))
continue;
words.add(buf);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (br != null)
br.close();
} catch (IOException e) {
}
}
return words;
}
/**
* 增加停頓詞
*
* @param words
*/
private static void addStopWord(final List<String> words) {
if (words != null && words.size() > 0) {
char[] chs;
for (String curr : words) {
chs = curr.toCharArray();
for (char c : chs) {
stopwdSet.add(charConvert(c));
}
}
}
}
/**
* 添加DFA節(jié)點(diǎn)
* @param words
*/
private static void addSensitiveWord(final List<String> words) {
if (words != null && words.size() > 0) {
char[] chs;
int fchar;
int lastIndex;
WordNode fnode; // 首字母節(jié)點(diǎn)
for (String curr : words) {
chs = curr.toCharArray();
fchar = charConvert(chs[0]);
if (!set.contains(fchar)) {// 沒(méi)有首字定義
set.add(fchar);// 首字標(biāo)志位 可重復(fù)add,反正判斷了,不重復(fù)了
fnode = new WordNode(fchar, chs.length == 1);
nodes.put(fchar, fnode);
} else {
fnode = nodes.get(fchar);
if (!fnode.isLast() && chs.length == 1)
fnode.setLast(true);
}
lastIndex = chs.length - 1;
for (int i = 1; i < chs.length; i++) {
fnode = fnode.addIfNoExist(charConvert(chs[i]), i == lastIndex);
}
}
}
}
/**
* 過(guò)濾判斷 將敏感詞轉(zhuǎn)化為成屏蔽詞
* @param src
* @return
*/
public static final String doFilter(final String src) {
char[] chs = src.toCharArray();
int length = chs.length;
int currc;
int k;
WordNode node;
for (int i = 0; i < length; i++) {
currc = charConvert(chs[i]);
if (!set.contains(currc)) {
continue;
}
node = nodes.get(currc);// 日 2
if (node == null)// 其實(shí)不會(huì)發(fā)生,習(xí)慣性寫(xiě)上了
continue;
boolean couldMark = false;
int markNum = -1;
if (node.isLast()) {// 單字匹配(日)
couldMark = true;
markNum = 0;
}
// 繼續(xù)匹配(日你/日你妹),以長(zhǎng)的優(yōu)先
// 你-3 妹-4 夫-5
k = i;
for (; ++k < length;) {
int temp = charConvert(chs[k]);
if (stopwdSet.contains(temp))
continue;
node = node.querySub(temp);
if (node == null)// 沒(méi)有了
break;
if (node.isLast()) {
couldMark = true;
markNum = k - i;// 3-2
}
}
if (couldMark) {
for (k = 0; k <= markNum; k++) {
chs[k + i] = SIGN;
}
i = i + markNum;
}
}
return new String(chs);
}
/**
* 是否包含敏感詞
* @param src
* @return
*/
public static final boolean isContains(final String src) {
char[] chs = src.toCharArray();
int length = chs.length;
int currc;
int k;
WordNode node;
for (int i = 0; i < length; i++) {
currc = charConvert(chs[i]);
if (!set.contains(currc)) {
continue;
}
node = nodes.get(currc);// 日 2
if (node == null)// 其實(shí)不會(huì)發(fā)生,習(xí)慣性寫(xiě)上了
continue;
boolean couldMark = false;
if (node.isLast()) {// 單字匹配(日)
couldMark = true;
}
// 繼續(xù)匹配(日你/日你妹),以長(zhǎng)的優(yōu)先
// 你-3 妹-4 夫-5
k = i;
for (; ++k < length;) {
int temp = charConvert(chs[k]);
if (stopwdSet.contains(temp))
continue;
node = node.querySub(temp);
if (node == null)// 沒(méi)有了
break;
if (node.isLast()) {
couldMark = true;
}
}
if (couldMark) {
return true;
}
}
return false;
}
/**
* 大寫(xiě)轉(zhuǎn)化為小寫(xiě) 全角轉(zhuǎn)化為半角
*
* @param src
* @return
*/
private static int charConvert(char src) {
int r = BCConvert.qj2bj(src);
return (r >= 'A' && r <= 'Z') ? r + 32 : r;
}
}
其中:
isContains :是否包含敏感詞
doFilter:過(guò)濾敏感詞
2、WordNode敏感詞節(jié)點(diǎn)
package org.andy.sensitivewdfilter;
import java.util.LinkedList;
import java.util.List;
/**
* 創(chuàng)建時(shí)間:2016年8月30日 下午3:07:45
*
* @author andy
* @version 2.2
*/
public class WordNode {
private int value; // 節(jié)點(diǎn)名稱(chēng)
private List<WordNode> subNodes; // 子節(jié)點(diǎn)
private boolean isLast;// 默認(rèn)false
public WordNode(int value) {
this.value = value;
}
public WordNode(int value, boolean isLast) {
this.value = value;
this.isLast = isLast;
}
/**
*
* @param subNode
* @return 就是傳入的subNode
*/
private WordNode addSubNode(final WordNode subNode) {
if (subNodes == null)
subNodes = new LinkedList<WordNode>();
subNodes.add(subNode);
return subNode;
}
/**
* 有就直接返回該子節(jié)點(diǎn), 沒(méi)有就創(chuàng)建添加并返回該子節(jié)點(diǎn)
*
* @param value
* @return
*/
public WordNode addIfNoExist(final int value, final boolean isLast) {
if (subNodes == null) {
return addSubNode(new WordNode(value, isLast));
}
for (WordNode subNode : subNodes) {
if (subNode.value == value) {
if (!subNode.isLast && isLast)
subNode.isLast = true;
return subNode;
}
}
return addSubNode(new WordNode(value, isLast));
}
public WordNode querySub(final int value) {
if (subNodes == null) {
return null;
}
for (WordNode subNode : subNodes) {
if (subNode.value == value)
return subNode;
}
return null;
}
public boolean isLast() {
return isLast;
}
public void setLast(boolean isLast) {
this.isLast = isLast;
}
@Override
public int hashCode() {
return value;
}
}
三、測(cè)試結(jié)果

項(xiàng)目包含敏感詞庫(kù),源碼,停頓詞庫(kù)等,只需運(yùn)行maven打jar包直接可運(yùn)行。
項(xiàng)目源碼:sensitivewd-filter_jb51.rar
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- JavaEE Filter敏感詞過(guò)濾的方法實(shí)例詳解
- JavaWeb中的filter過(guò)濾敏感詞匯案例詳解
- java利用DFA算法實(shí)現(xiàn)敏感詞過(guò)濾功能
- Java實(shí)戰(zhàn)之敏感詞過(guò)濾器
- JAVA使用前綴樹(shù)(Tire樹(shù))實(shí)現(xiàn)敏感詞過(guò)濾、詞典搜索
- Java使用DFA算法實(shí)現(xiàn)敏感詞過(guò)濾的示例代碼
- Java 過(guò)濾器實(shí)現(xiàn)敏感詞匯過(guò)濾功能
- Java數(shù)據(jù)敏感詞轉(zhuǎn)換成符號(hào)的方法詳解
- Java 敏感詞檢測(cè)工具的實(shí)現(xiàn)
相關(guān)文章
JUC循環(huán)屏障CyclicBarrier與CountDownLatch區(qū)別詳解
這篇文章主要為大家介紹了JUC循環(huán)屏障CyclicBarrier與CountDownLatch區(qū)別詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
Java數(shù)據(jù)結(jié)構(gòu)之鏈表詳解
本篇文章我們將講解一種新型的數(shù)據(jù)結(jié)構(gòu)—鏈表,鏈表是一種使用廣泛的通用數(shù)據(jù)結(jié)構(gòu),它可以用來(lái)作為實(shí)現(xiàn)棧,隊(duì)列等數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ).文中有非常詳細(xì)的介紹,需要的朋友可以參考下2021-05-05
Spring Boot利用Java Mail實(shí)現(xiàn)郵件發(fā)送
這篇文章主要為大家詳細(xì)介紹了Spring Boot利用Java Mail實(shí)現(xiàn)郵件發(fā)送,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02
Java中Object toString方法簡(jiǎn)介_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Object類(lèi)在Java里面是一個(gè)比較特殊的類(lèi),JAVA為了組織這個(gè)類(lèi)組織得比較方便,它提供了一個(gè)最根上的類(lèi),相當(dāng)于所有的類(lèi)都是從這個(gè)類(lèi)繼承,這個(gè)類(lèi)就叫Object。接下來(lái)通過(guò)本文給大家介紹Object toString方法,需要的的朋友參考下吧2017-05-05

