Sunday For Stream

前言:在讀取kom壓縮文件的文件頭裏,有一段xml文本記錄文件列表,現在需要讀取這段xml文本。由於1個kom文件有幾個G的大小,所以需要一個針對Stream用的Sunday算法來進行匹配,但是沒有找到針對Stream的Sunday算法(其實是懶得找),於是就決定自己實現一個來加深對Sunday算法的理解。

  在看下面的內容前,你必須對Sunday算法的思想有所瞭解,不然看了也差不多是白看。

  如果你只是想要複製代碼,請直接看最後。

  如果發現了什麼Bug,或者有可以優化的地方,十分歡迎指正。

預定義:

  1. Stream的指針是從左向右進行移動的(鄙視那些說向前向後移動的人,都不說明哪個方向是前,哪個方向是後)。

  2. source指待匹配的Stream,是要從這裏面找你想要的子串,下面統一叫主串。

  3. pattern指目標的Byte數組,是要從主串中查找的東西,下面統一叫模式串。

  4. 下面定義了兩個子串:child1和child2,他們長度相等,且比pattern的長度大1(爲什麼這樣定義?因爲這是Sunday算法)。child1和child2總是相連的,而且child1在child2前面。

  5. 下面提到的k,是指pattern在子串的右一位(child2子串末位),而且,這個k總是在子串child2上。

  6. i是在子串child1和child2上滑動的指針(Index)

  7. j是在pattern上滑動的指針(Index),而且每開始新的匹配時,總有j=pattern.Length-1。原因是,這裏匹配時,是從右向左匹配的。事實上網上很多Sunday算法都是反向匹配的(比如這裏source的指針從左向右走,匹配時就是在子串中從右向左匹配,這樣更好,這裏就不詳細解釋了)。

  8. 這裏使用到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子串中的字符。

wKioL1gXSVaCZ1ZhAAA5pKidXAY305.png-wh_50

wKiom1gXSSKg1IGPAAA4Dcuzsd8441.png-wh_50

下面是代碼的實現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的右一位。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章