使用DFA實現文字過濾
Dfa和文字過濾
文字過濾是一般大型網站必不可少的一個功能,而且很多文字類網站更是需要。那麼如何設計一個 高效的文字過濾系統就是非常重要的了。
文字過濾需求簡要描述:判斷集合A中哪些子集屬於集合B,拿javaeye來說,如果用戶發 表一篇文章(集合A),我們需要判斷這篇文章裏是否存在一些關鍵字是屬於集合B,B一般來說就是違禁詞列表。
看到這裏,沒有接觸過的同學可能會想到contains,正則之類的方法,但是很遺憾,這些 方法都是行不通的。唯一比較好的算法是DFA。
學過編譯原理的同學們一定知道,在詞法分析階段將源代碼中的文本變成語法的集合就是通過確定有限自動機實現的。但是DFA並不只是詞法分析裏用到,DFA 的用途非常的廣泛,並不侷限在計算機領域。
DFA的基本功能是可以通過event和當前的state得到下一個state,即event+state=nextstate,
我們來看一張到處都能找到的狀態圖:
大寫字母是狀態,小寫字母是動作:我們可以看到S+a=U,U+a=Q,S+b=V等等。一般情況下我們可以用矩陣來表示整個狀態轉移過程:
---------------
狀態/字符 a b
S U V
U Q V
V U Q
Q Q Q
但是表示狀態圖可以有很多數據結構,上面的矩陣只是一個便於理解的簡單例子。而接下來在本文提到的文字過濾系統中會使用另外的數據結構來實現自動 機模型
二,文字過濾
在文字過濾系統中,爲了能夠應付較高的併發,有一個目標比較重要,就是儘量的減少計算,而在DFA中,基本沒有什麼計算,有的只是狀態的轉移。而 要把違禁文字列表構造成一個狀態機,用矩陣來實現是比較麻煩的,下面介紹一種比較簡單的實現方式,就是樹結構。
所有的違禁詞其本質來說是有ascii碼組成的,而待過濾文本其本質也是ascii碼的集合,比如說:
輸入是A=[101,102,105,97,98,112,110]
違禁詞列表:
[102,105]
[98,112]
那麼我們的任務就是把上面兩個違禁詞構造成一個DFA,這樣輸入的A就可以通過在這個DFA上的轉移來實現違禁詞查找的功能。
樹結構實現這個DFA的基於的基本方法是數組的index和數組value之間的關係(在雙數組trie中同樣是基於這一基本方法)
那麼102其實可以看作一個數組索引,而105是102這個索引指向的下一個數組中的一個索引,105後面沒有值了,那就代表這個違禁詞結束了。
通過這樣一種方式,就可以構造出一顆DFA的樹結構表示。
接着遍歷輸入文本中的每一個byte,然後在DFA中作狀態轉移就可以判斷出一個違禁詞是否出現在輸入文本中。
- public class DFA {
- private String[] arr = {"DFA", "噁心", "DA"};
- private Node rootNode = new Node('R');
- private String content = "Hello DFA World DFA, HaHa! 噁心";
- private List<String> words = new ArrayList<String>();
- private List<String> word = new ArrayList<String>();
- int a = 0;
- private void searchWord() {
- char[] chars = content.toCharArray();
- Node node = rootNode;
- while(a<chars.length) {
- node = findNode(node,chars[a]);
- if(node == null) {
- node = rootNode;
- a = a - word.size();
- word.clear();
- } else if(node.flag == 1) {
- word.add(String.valueOf(chars[a]));
- StringBuffer sb = new StringBuffer();
- for(String str : word) {
- sb.append(str);
- }
- words.add(sb.toString());
- a = a - word.size() + 1;
- word.clear();
- node = rootNode;
- } else {
- word.add(String.valueOf(chars[a]));
- }
- a++;
- }
- }
- private void createTree() {
- for(String str : arr) {
- char[] chars = str.toCharArray();
- if(chars.length > 0)
- insertNode(rootNode, chars, 0);
- }
- }
- private void insertNode(Node node, char[] cs, int index) {
- Node n = findNode(node, cs[index]);
- if(n == null) {
- n = new Node(cs[index]);
- node.nodes.add(n);
- }
- if(index == (cs.length-1))
- n.flag = 1;
- index++;
- if(index<cs.length)
- insertNode(n, cs, index);
- }
- private Node findNode(Node node, char c) {
- List<Node> nodes = node.nodes;
- Node rn = null;
- for(Node n : nodes) {
- if(n.c==c) {
- rn = n;
- break;
- }
- }
- return rn;
- }
- public static void main(String[] args) {
- DFA dfa = new DFA();
- dfa.createTree();
- dfa.searchWord();
- System.out.println(dfa.words);
- }
- private static class Node {
- public char c;
- public int flag; //1:表示終結,0:延續
- public List<Node> nodes = new ArrayList<Node>();
- public Node(char c) {
- this.c = c;
- this.flag = 0;
- }
- public Node(char c, int flag) {
- this.c = c;
- this.flag = flag;
- }
- }
- }