3

这是 C# 相关的。我们有一种情况,我们需要将整个源流复制到目标流中,最后 16 个字节除外。

编辑:流的范围可达 40GB,因此不能进行一些静态字节 [] 分配(例如:.ToArray())

查看MSDN 文档,似乎只有当返回值为 0 时,我们才能可靠地0确定流的结束。之间的返回值the requested size可以暗示字节“当前不可用”(这到底是什么意思?)

目前它复制每个字节如下。inStream并且outStream是通用的——可以是内存、磁盘或网络流(实际上还有更多)。

public static void StreamCopy(Stream inStream, Stream outStream)
{
    var buffer = new byte[8*1024];
    var last16Bytes = new byte[16];
    int bytesRead;
    while ((bytesRead = inStream.Read(buffer, 0, buffer.Length)) > 0)
    {
        outStream.Write(buffer, 0, bytesRead);
    }
    // Issues:
    // 1. We already wrote the last 16 bytes into 
    //    outStream (possibly over the n/w)
    // 2. last16Bytes = ? (inStream may not necessarily support rewinding)
}

确保复制除最后 16 个以外的所有内容的可靠方法是什么?我可以考虑在 inStream 上使用Positionand但在MSDN上有一个陷阱说Length

如果从 Stream 派生的类不支持查找,则对 Length、SetLength、Position 和 Seek 的调用将引发 NotSupportedException。.

4

3 回答 3

6
  1. 从输入流中读取1n个字节。1

  2. 将字节附加到循环缓冲区2

  3. 将循环缓冲区中的前max(0, b - 16)个字节写入输出流,其中b是循环缓冲区中的字节数。

  4. 从循环缓冲区中删除刚刚写入的字节。

  5. 转到步骤 1。

1这就是该Read方法的作用——如果您调用int n = Read(buffer, 0, 500);它,它将读取 1 到 500 个字节buffer并返回读取的字节数。如果Read返回 0,则您已到达流的末尾。

2为获得最佳性能,您可以将字节直接从输入流中读取到循环缓冲区中。这有点棘手,因为您必须处理缓冲区底层数组中的环绕。

于 2013-06-18T16:39:00.680 回答
2

以下解决方案快速且经过测试。希望它有用。它使用了您已经想到的双缓冲想法。 编辑:简化循环删除将第一次迭代与其余迭代分开的条件。

public static void StreamCopy(Stream inStream, Stream outStream) {
     // Define the size of the chunk to copy during each iteration (1 KiB)
     const int blockSize = 1024;
     const int bytesToOmit = 16;

     const int buffSize = blockSize + bytesToOmit;

     // Generate working buffers
     byte[] buffer1 = new byte[buffSize];
     byte[] buffer2 = new byte[buffSize];

     // Initialize first iteration
     byte[] curBuffer = buffer1;
     byte[] prevBuffer = null;

     int bytesRead;

     // Attempt to fully fill the buffer
     bytesRead = inStream.Read(curBuffer, 0, buffSize);
     if( bytesRead == buffSize ) {
        // We succesfully retrieved a whole buffer, we will output
        // only [blockSize] bytes, to avoid writing to the last
        // bytes in the buffer in case the remaining 16 bytes happen to 
        // be the last ones
        outStream.Write(curBuffer, 0, blockSize);
     } else {
        // We couldn't retrieve the whole buffer
        int bytesToWrite = bytesRead - bytesToOmit;
        if( bytesToWrite > 0 ) {
           outStream.Write(curBuffer, 0, bytesToWrite);
        }
        // There's no more data to process
        return;
     }

     curBuffer = buffer2;
     prevBuffer = buffer1;

     while( true ) {
        // Attempt again to fully fill the buffer
        bytesRead = inStream.Read(curBuffer, 0, buffSize);
        if( bytesRead == buffSize ) {
           // We retrieved the whole buffer, output first the last 16 
           // bytes of the previous buffer, and output just [blockSize]
           // bytes from the current buffer
           outStream.Write(prevBuffer, blockSize, bytesToOmit);
           outStream.Write(curBuffer, 0, blockSize);
        } else {
           // We could not retrieve a complete buffer 
           if( bytesRead <= bytesToOmit ) {
              // The bytes to output come solely from the previous buffer
              outStream.Write(prevBuffer, blockSize, bytesRead);
           } else {
              // The bytes to output come from the previous buffer and
              // the current buffer
              outStream.Write(prevBuffer, blockSize, bytesToOmit);
              outStream.Write(curBuffer, 0, bytesRead - bytesToOmit);
           }
           break;
        }
        // swap buffers for next iteration
        byte[] swap = prevBuffer;
        prevBuffer = curBuffer;
        curBuffer = swap;
     }
  }

static void Assert(Stream inStream, Stream outStream) {
   // Routine that tests the copy worked as expected
         inStream.Seek(0, SeekOrigin.Begin);
         outStream.Seek(0, SeekOrigin.Begin);
         Debug.Assert(outStream.Length == Math.Max(inStream.Length - bytesToOmit, 0));
         for( int i = 0; i < outStream.Length; i++ ) {
            int byte1 = inStream.ReadByte();
            int byte2 = outStream.ReadByte();
            Debug.Assert(byte1 == byte2);
         }

      }

一个更简单的编码解决方案,但速度较慢,因为它可以在字节级别工作,是在输入流和输出流之间使用中间队列。该进程将首先从输入流中读取 16 个字节并将其排入队列。然后它将遍历剩余的输入字节,从输入流中读取一个字节,将其入队,然后将一个字节出队。出队的字节将被写入输出流,直到输入流中的所有字节都被处理。不需要的 16 个字节应该留在中间队列中。

希望这可以帮助!

=)

于 2013-06-18T23:58:43.833 回答
0

使用循环缓冲区听起来不错,但 .NET 中没有循环缓冲区类,这意味着无论如何都要附加代码。我最终得到了以下算法,有点map and copy- 我认为它很简单。为了在此处进行自我描述,变量名称比平时长。

这流过缓冲区

[outStream] <== [tailBuf] <== [mainBuf] <== [inStream]

public byte[] CopyStreamExtractLastBytes(Stream inStream, Stream outStream,
                                         int extractByteCount)
{
    //var mainBuf = new byte[1024*4]; // 4K buffer ok for network too
    var mainBuf = new byte[4651]; // nearby prime for testing

    int mainBufValidCount;
    var tailBuf = new byte[extractByteCount];
    int tailBufValidCount = 0;

    while ((mainBufValidCount = inStream.Read(mainBuf, 0, mainBuf.Length)) > 0)
    {
        // Map: how much of what (passthru/tail) lives where (MainBuf/tailBuf)
        // more than tail is passthru
        int totalPassthruCount = Math.Max(0, tailBufValidCount + 
                                    mainBufValidCount - extractByteCount);
        int tailBufPassthruCount = Math.Min(tailBufValidCount, totalPassthruCount);
        int tailBufTailCount = tailBufValidCount - tailBufPassthruCount;
        int mainBufPassthruCount = totalPassthruCount - tailBufPassthruCount;
        int mainBufResidualCount = mainBufValidCount - mainBufPassthruCount;

        // Copy: Passthru must be flushed per FIFO order (tailBuf then mainBuf)
        outStream.Write(tailBuf, 0, tailBufPassthruCount);
        outStream.Write(mainBuf, 0, mainBufPassthruCount);

        // Copy: Now reassemble/compact tail into tailBuf
        var tempResidualBuf = new byte[extractByteCount];
        Array.Copy(tailBuf, tailBufPassthruCount, tempResidualBuf, 0, 
                      tailBufTailCount);
        Array.Copy(mainBuf, mainBufPassthruCount, tempResidualBuf, 
                      tailBufTailCount, mainBufResidualCount);
        tailBufValidCount = tailBufTailCount + mainBufResidualCount;
        tailBuf = tempResidualBuf;
    }
    return tailBuf;
}
于 2013-06-21T20:10:07.393 回答