【问题标题】:Best way to find position in the Stream where given byte sequence starts在 Stream 中找到给定字节序列开始位置的最佳方法
【发布时间】:2021-06-04 01:53:23
【问题描述】:

您如何看待在给定字节序列开始的 System.Stream 中找到位置的最佳方法(第一次出现):

public static long FindPosition(Stream stream, byte[] byteSequence)
{
    long position = -1;

    /// ???
    return position;
}

附言首选最简单但最快的解决方案。 :)

【问题讨论】:

  • 你的问题令人困惑......你在找什么?流中特定的字节序列?
  • 我认为应该更新问题的标题。 Stream 被错误拼写为 Steam,这使得它看起来像是一个应该标记为 Valve 的问题。
  • @chollida:实际上,我提出这个问题只是为了解决这个问题。
  • 实际上我正在寻找流中的 guid。
  • 内存有问题吗?还是可以将整个流读入一个字节数组?

标签: c# algorithm find stream bytearray


【解决方案1】:

我已经达到了这个解决方案。

我用 3.050 KB38803 lines 的 ASCII 文件做了一些基准测试。 通过在文件的最后一行搜索 byte array22 bytes,我在大约 2.28 秒内得到了结果(在慢速/旧机器中)。

public static long FindPosition(Stream stream, byte[] byteSequence)
{
    if (byteSequence.Length > stream.Length)
        return -1;

    byte[] buffer = new byte[byteSequence.Length];

    using (BufferedStream bufStream = new BufferedStream(stream, byteSequence.Length))
    {
        int i;
        while ((i = bufStream.Read(buffer, 0, byteSequence.Length)) == byteSequence.Length)
        {
            if (byteSequence.SequenceEqual(buffer))
                return bufStream.Position - byteSequence.Length;
            else
                bufStream.Position -= byteSequence.Length - PadLeftSequence(buffer, byteSequence);
        }
    }

    return -1;
}

private static int PadLeftSequence(byte[] bytes, byte[] seqBytes)
{
    int i = 1;
    while (i < bytes.Length)
    {
        int n = bytes.Length - i;
        byte[] aux1 = new byte[n];
        byte[] aux2 = new byte[n];
        Array.Copy(bytes, i, aux1, 0, n);
        Array.Copy(seqBytes, aux2, n);
        if (aux1.SequenceEqual(aux2))
            return i;
        i++;
    }
    return i;
}

【讨论】:

  • 为了将来参考,PadLeftSequence 正在搜索导致SequenceEqual 返回 false 的第一个不匹配字节。对我来说,这似乎是一种微观优化,因为人们会期望SequenceEqual无论如何都会提前返回不匹配。免责声明:我没有做过任何测量,这只是意见。
  • 它不是仅在序列位于长度乘法索引处时才有效吗?我的意思是,将找不到索引 4 处的 6 个字节序列?
【解决方案2】:

如果您将流视为另一个字节序列,则可以像搜索字符串一样搜索它。 Wikipedia 对此有一篇很棒的文章。 Boyer-Moore 是一个很好的简单算法。

这是我用 Java 编写的快速技巧。它有效,如果不是 Boyer-Moore,它也非常接近。希望能帮助到你 ;)

public static final int BUFFER_SIZE = 32;

public static int [] buildShiftArray(byte [] byteSequence){
    int [] shifts = new int[byteSequence.length];
    int [] ret;
    int shiftCount = 0;
    byte end = byteSequence[byteSequence.length-1];
    int index = byteSequence.length-1;
    int shift = 1;

    while(--index >= 0){
        if(byteSequence[index] == end){
            shifts[shiftCount++] = shift;
            shift = 1;
        } else {
            shift++;
        }
    }
    ret = new int[shiftCount];
    for(int i = 0;i < shiftCount;i++){
        ret[i] = shifts[i];
    }
    return ret;
}

public static byte [] flushBuffer(byte [] buffer, int keepSize){
    byte [] newBuffer = new byte[buffer.length];
    for(int i = 0;i < keepSize;i++){
        newBuffer[i] = buffer[buffer.length - keepSize + i];
    }
    return newBuffer;
}

public static int findBytes(byte [] haystack, int haystackSize, byte [] needle, int [] shiftArray){
    int index = needle.length;
    int searchIndex, needleIndex, currentShiftIndex = 0, shift;
    boolean shiftFlag = false;

    index = needle.length;
    while(true){
        needleIndex = needle.length-1;
        while(true){
            if(index >= haystackSize)
                return -1;
            if(haystack[index] == needle[needleIndex])
                break;
            index++;
        }
        searchIndex = index;
        needleIndex = needle.length-1;
        while(needleIndex >= 0 && haystack[searchIndex] == needle[needleIndex]){
            searchIndex--;
            needleIndex--;
        }
        if(needleIndex < 0)
            return index-needle.length+1;
        if(shiftFlag){
            shiftFlag = false;
            index += shiftArray[0];
            currentShiftIndex = 1;
        } else if(currentShiftIndex >= shiftArray.length){
            shiftFlag = true;
            index++;
        } else{
            index += shiftArray[currentShiftIndex++];
        }           
    }
}

public static int findBytes(InputStream stream, byte [] needle){
    byte [] buffer = new byte[BUFFER_SIZE];
    int [] shiftArray = buildShiftArray(needle);
    int bufferSize, initBufferSize;
    int offset = 0, init = needle.length;
    int val;

    try{
        while(true){
            bufferSize = stream.read(buffer, needle.length-init, buffer.length-needle.length+init);
            if(bufferSize == -1)
                return -1;
            if((val = findBytes(buffer, bufferSize+needle.length-init, needle, shiftArray)) != -1)
                return val+offset;
            buffer = flushBuffer(buffer, needle.length);
            offset += bufferSize-init;
            init = 0;
        }
    } catch (IOException e){
        e.printStackTrace();
    }
    return -1;
}

【讨论】:

  • 它可能不是最简单的,但它非常快。它认为考虑到从流中读取的限制不允许简单,如果你想要速度。但我希望我的代码可以减轻您的一些麻烦,或者将来对某人有所帮助。
  • 似乎 findBytes 中的 initBufferSize 变量未被使用。
  • 注意:这个解决方案似乎是用 Java 编写的,而 OP 要求使用 C#
【解决方案3】:

您基本上需要保持与byteSequence 大小相同的缓冲区,这样一旦您发现流中的“下一个字节”匹配,您就可以检查其余部分,但仍然返回到“下一个字节” " byte 如果不是实际匹配。

老实说,无论您做什么,都可能有点繁琐:(

【讨论】:

    【解决方案4】:

    我需要自己做,已经开始了,不喜欢上面的解决方案。我特别需要找到搜索字节序列的结束位置。在我的情况下,我需要快进流直到该字节序列之后。但是你也可以使用我的解决方案来解决这个问题:

    var afterSequence = stream.ScanUntilFound(byteSequence);
    var beforeSequence = afterSequence - byteSequence.Length;
    

    这是 StreamExtensions.cs

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace System
    {
    
        static class StreamExtensions
        {
            /// <summary>
            /// Advances the supplied stream until the given searchBytes are found, without advancing too far (consuming any bytes from the stream after the searchBytes are found).
            /// Regarding efficiency, if the stream is network or file, then MEMORY/CPU optimisations will be of little consequence here.
            /// </summary>
            /// <param name="stream">The stream to search in</param>
            /// <param name="searchBytes">The byte sequence to search for</param>
            /// <returns></returns>
            public static int ScanUntilFound(this Stream stream, byte[] searchBytes)
            {
                // For this class code comments, a common example is assumed:
                // searchBytes are {1,2,3,4} or 1234 for short
                // # means value that is outside of search byte sequence
    
                byte[] streamBuffer = new byte[searchBytes.Length];
                int nextRead = searchBytes.Length;
                int totalScannedBytes = 0;
    
                while (true)
                {
                    FillBuffer(stream, streamBuffer, nextRead);
                    totalScannedBytes += nextRead; //this is only used for final reporting of where it was found in the stream
    
                    if (ArraysMatch(searchBytes, streamBuffer, 0))
                        return totalScannedBytes; //found it
    
                    nextRead = FindPartialMatch(searchBytes, streamBuffer);
                }
            }
    
            /// <summary>
            /// Check all offsets, for partial match. 
            /// </summary>
            /// <param name="searchBytes"></param>
            /// <param name="streamBuffer"></param>
            /// <returns>The amount of bytes which need to be read in, next round</returns>
            static int FindPartialMatch(byte[] searchBytes, byte[] streamBuffer)
            {
                // 1234 = 0 - found it. this special case is already catered directly in ScanUntilFound            
                // #123 = 1 - partially matched, only missing 1 value
                // ##12 = 2 - partially matched, only missing 2 values
                // ###1 = 3 - partially matched, only missing 3 values
                // #### = 4 - not matched at all
    
                for (int i = 1; i < searchBytes.Length; i++)
                {
                    if (ArraysMatch(searchBytes, streamBuffer, i))
                    {
                        // EG. Searching for 1234, have #123 in the streamBuffer, and [i] is 1
                        // Output: 123#, where # will be read using FillBuffer next. 
                        Array.Copy(streamBuffer, i, streamBuffer, 0, searchBytes.Length - i);
                        return i; //if an offset of [i], makes a match then only [i] bytes need to be read from the stream to check if there's a match
                    }
                }
    
                return 4;
            }
    
            /// <summary>
            /// Reads bytes from the stream, making sure the requested amount of bytes are read (streams don't always fulfill the full request first time)
            /// </summary>
            /// <param name="stream">The stream to read from</param>
            /// <param name="streamBuffer">The buffer to read into</param>
            /// <param name="bytesNeeded">How many bytes are needed. If less than the full size of the buffer, it fills the tail end of the streamBuffer</param>
            static void FillBuffer(Stream stream, byte[] streamBuffer, int bytesNeeded)
            {
                // EG1. [123#] - bytesNeeded is 1, when the streamBuffer contains first three matching values, but now we need to read in the next value at the end 
                // EG2. [####] - bytesNeeded is 4
    
                var bytesAlreadyRead = streamBuffer.Length - bytesNeeded; //invert
                while (bytesAlreadyRead < streamBuffer.Length)
                {
                    bytesAlreadyRead += stream.Read(streamBuffer, bytesAlreadyRead, streamBuffer.Length - bytesAlreadyRead);
                }
            }
    
            /// <summary>
            /// Checks if arrays match exactly, or with offset. 
            /// </summary>
            /// <param name="searchBytes">Bytes to search for. Eg. [1234]</param>
            /// <param name="streamBuffer">Buffer to match in. Eg. [#123] </param>
            /// <param name="startAt">When this is zero, all bytes are checked. Eg. If this value 1, and it matches, this means the next byte in the stream to read may mean a match</param>
            /// <returns></returns>
            static bool ArraysMatch(byte[] searchBytes, byte[] streamBuffer, int startAt)
            {
                for (int i = 0; i < searchBytes.Length - startAt; i++)
                {
                    if (searchBytes[i] != streamBuffer[i + startAt])
                        return false;
                }
                return true;
            }
        }
    }
    

    【讨论】:

      【解决方案5】:

      有点老问题,但这是我的答案。我发现,与一次只读一个然后从那里开始相比,阅读块然后在其中搜索效率极低。

      此外,IIRC,如果序列的一部分在一个块中读取而一半在另一个块中,则接受的答案将失败 - 例如,给定 12345,搜索 23,它将读取 12,不匹配,然后读取 34,不匹配,等等。 .. 还没有尝试过,因为它需要 net 4.0。无论如何,这要简单得多,而且可能要快得多。

      static long ReadOneSrch(Stream haystack, byte[] needle)
      {
          int b;
          long i = 0;
          while ((b = haystack.ReadByte()) != -1)
          {
              if (b == needle[i++])
              {
                  if (i == needle.Length)
                      return haystack.Position - needle.Length;
              }
              else
                  i = b == needle[0] ? 1 : 0;
          }
      
          return -1;
      }
      

      【讨论】:

      • 你的代码不正确。考虑 haystack = [ 2,1,2,1,1 ], needle = [ 2,1,1 ]。您的代码返回 -1,但正确答案是 2
      【解决方案6】:
      static long Search(Stream stream, byte[] pattern)
      {
          long start = -1;
      
          stream.Seek(0, SeekOrigin.Begin);
      
          while(stream.Position < stream.Length)
          {
              if (stream.ReadByte() != pattern[0])
                  continue;
      
              start = stream.Position - 1;
      
              for (int idx = 1; idx < pattern.Length; idx++)
              {
                  if (stream.ReadByte() != pattern[idx])
                  {
                      start = -1;
                      break;
                  }
              }
      
              if (start > -1)
              {
                  return start;
              }
          }
      
          return start;
      }
      

      【讨论】:

      • 欢迎来到堆栈溢出。尽量避免只回答代码,并对您的代码进行一些解释。
      猜你喜欢
      • 2010-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多