前言:在讀取kom壓縮文件的文件頭裏,有一段xml文本記錄文件列表,現在需要讀取這段xml文本。由於1個kom文件有幾個G的大小,所以需要一個針對Stream用的Sunday算法來進行匹配,但是沒有找到針對Stream的Sunday算法(其實是懶得找),於是就決定自己實現一個來加深對Sunday算法的理解。
在看下面的內容前,你必須對Sunday算法的思想有所瞭解,不然看了也差不多是白看。
如果你只是想要複製代碼,請直接看最後。
如果發現了什麼Bug,或者有可以優化的地方,十分歡迎指正。
預定義:
Stream的指針是從左向右進行移動的(鄙視那些說向前向後移動的人,都不說明哪個方向是前,哪個方向是後)。
source指待匹配的Stream,是要從這裏面找你想要的子串,下面統一叫主串。
pattern指目標的Byte數組,是要從主串中查找的東西,下面統一叫模式串。
下面定義了兩個子串:child1和child2,他們長度相等,且比pattern的長度大1(爲什麼這樣定義?因爲這是Sunday算法)。child1和child2總是相連的,而且child1在child2前面。
下面提到的k,是指pattern在子串的右一位(child2子串末位),而且,這個k總是在子串child2上。
i是在子串child1和child2上滑動的指針(Index)
j是在pattern上滑動的指針(Index),而且每開始新的匹配時,總有j=pattern.Length-1。原因是,這裏匹配時,是從右向左匹配的。事實上網上很多Sunday算法都是反向匹配的(比如這裏source的指針從左向右走,匹配時就是在子串中從右向左匹配,這樣更好,這裏就不詳細解釋了)。
這裏使用到Next數組,這裏Next數組參照使用Next數組的Sunday算法:初始化時next[t]=pattern.Length+1,表示下一次k可以跳pattern.Length+1個值,而next[pattern[t]]=pattern.Length-t。這裏不詳細解釋。網上查到少部分Sunday使用Next數組,但多數不使用Next數組,其實效果都一樣,只是Next數組用內存空間來換取一少部分的性能。
下面以主串asdfqwvasdfqwevasdczxfwqfxcvzxcvqwerczxcvqwerewqfz和模式中zxcsaef作爲例子來演示匹配過程。因爲排版的問題,可能出現錯位的情況,所以這裏給你們展示圖片。
child左邊的豎線|的Index=0,右邊豎線的Index=child.Length-1,也就是說,兩條豎線指向的字符也屬於child子串中的字符。
下面是代碼的實現C#,Java和C#差不多(只要把裏面的Int32替換成int,把Byte替換成byte,再把Boolean替換成boolean就是完整的Java代碼了(大概))。裏面關鍵的地方都有寫註釋,需要另外說明的是,爲了節省空間,定義了一個temp數組指針,temp數組指針在新的匹配時是指向child2的,當i<0時,temp指向child1並使得i=temp.Length-1。
因爲我的項目需要,這裏還傳入了一個pass流,用來儲存被pass掉的字符。而且,在檢索kom文件時,pattern是固定的,所以就再定義了一個next[]參數,這樣就不用每次都重新生成next數組了。
當我進行兩次Search時(XML頭和XML尾),中間那個pass流的內容就是xml的內容,直接Read出來就可以了,不用再次截取。
/// <summary> /// 生成Next數組 /// </summary> /// <param name="pattern"></param> /// <returns></returns> public static Int32[] GetNext(Byte[] pattern) { Int32[] next = new Int32[256]; for (Int32 x = 0; x < next.Length; x++) { next[x] = pattern.Length + 1; } for (Int32 x = 0; x < pattern.Length; x++) { next[pattern[x]] = pattern.Length - x; } return next; } /// <summary> /// 在Stream流中匹配模式串pattern /// </summary> /// <param name="source"></param> /// <param name="pass"></param> /// <param name="pattern"></param> /// <param name="next"></param> /// <returns></returns> public static Boolean SearchForStream(Stream source, Stream pass, Byte[] pattern, Int32[] next = null) { if (next == null) { next = GetNext(pattern); } // child1和child2是Stream上連續的子串,長度相同,且比pattern的長度大1. Byte[] child1 = new Byte[pattern.Length + 1]; Byte[] child2 = new Byte[child1.Length]; // temp用於指向child1和child2數組,並用於child1和child2數組的指針交換 Byte[] temp = null; // k是後綴(Sunday算法裏提到的子串的末位,這裏統一使用“後綴”來表示)在child2中的位置(Index),開始比較時,k = pattern.Length Int32 k = pattern.Length; // pos是k值下一次的位移值,預設爲0 Int32 pos = 0; // i是temp(child1和child2)上的指針(Index) Int32 i = 0; // j是pattern上的指針(Index),且每次開始新的匹配時,j都等於pattern.Length-1 Int32 j = 0; // 在Stream讀取的字節數 Int32 count = 0; // 結果 Boolean result = false; // 開始 while ((count = source.Read(child2, 0, child2.Length)) > -1) { temp = child2; k = k + pos; if (k >= child2.Length) { k = k - child2.Length; } if (k > count) { pass.Write(temp, 0, count); break;// false; } else if (k < count) { // 這裏不用判斷k==count的原因是,k==count時,下一個count=-1,跳出循環 pos = next[temp[k]]; } i = k - 1; j = pattern.Length - 1; do { if (i == -1) { i = pattern.Length; temp = child1; } } while (j > -1 && temp[i--] == pattern[j--]); if (j == -1) { pass.Write(child2, 0, k); source.Position = source.Position - count + k; result = true; break;//true; } else { pass.Write(child2, 0, count); } temp = child1; child1 = child2; child2 = temp; } // pattern在pass的末尾,source的Position在pattern之後 pass.Position = 0; return result; }
當匹配完成時,pattern在pass流的最後,source的Postion是pattern的右一位。