前言:在读取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子串中的字符。

Sunday For Stream_第1张图片

Sunday For Stream_第2张图片

下面是代码的实现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出来就可以了,不用再次截取。

/// 
       /// 生成Next数组
       /// 
       /// 
       /// 
        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;
        }

        /// 
        /// 在Stream流中匹配模式串pattern
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        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的右一位。