【问题标题】:How do I read exactly n bytes from a stream?如何从流中准确读取 n 个字节?
【发布时间】:2011-11-22 18:25:23
【问题描述】:

这比我最初想象的要复杂一些。我正在尝试从流中读取 n 个字节。

Read 的MSDN claims 不必返回 n 个字节,它必须返回至少 1 到最多 n 个字节,其中 0 个字节是到达流末尾的特殊情况。

通常,我使用类似的东西

var buf = new byte[size];
var count = stream.Read (buf, 0, size);

if (count != size) {
    buf = buf.Take (count).ToArray ();
}

yield return buf;

我希望精确到 size 字节,但根据规范 FileStream 也将被允许返回大量的 1 字节块。必须避免这种情况。

解决这个问题的一种方法是使用 2 个缓冲区,一个用于读取,一个用于收集块,直到我们获得请求的字节数。不过这有点麻烦。

我还查看了BinaryReader,但它的规范也没有明确说明肯定会返回 n 个字节。

澄清一下:当然,在流结束时返回的字节数可能小于size - 这不是问题。我只是在谈论不接收 n 字节,即使它们在流中可用。

【问题讨论】:

  • BinaryReader.ReadBytes(int) 返回请求的字节数;如果流提前结束,它将返回它在该点之前读取的内容(因此少于请求)。
  • @bosonix 那会很方便。您有此信息的来源吗?
  • 这是在MSDN页面msdn.microsoft.com/en-us/library/…中指定的,我也看了反汇编代码。
  • @bosonix 我明白了。那里说得很清楚,如果代码匹配,这似乎是最好的解决方案。我很困惑为什么我没有注意到该方法(显然在我提出这个问题时它是可用的),甚至 Marc Gravell 也没有建议它。

标签: c# .net stream


【解决方案1】:

一个更易读的版本:

int offset = 0;
while (offset < count)
{
    int read = stream.Read(buffer, offset, count - offset);
    if (read == 0)
        throw new System.IO.EndOfStreamException();
    offset += read;
}

或者写成Stream类的扩展方法:

public static class StreamUtils
{
    public static byte[] ReadExactly(this System.IO.Stream stream, int count)
    {
        byte[] buffer = new byte[count];
        int offset = 0;
        while (offset < count)
        {
            int read = stream.Read(buffer, offset, count - offset);
            if (read == 0)
                throw new System.IO.EndOfStreamException();
            offset += read;
        }
        System.Diagnostics.Debug.Assert(offset == count);
        return buffer;
    }
}

【讨论】:

    【解决方案2】:

    简单;你循环;

    int read, offset = 0;
    while(leftToRead > 0 && (read = stream.Read(buf, offset, leftToRead)) > 0) {
        leftToRead -= read;
        offset += read;
    }
    if(leftToRead > 0) throw new EndOfStreamException(); // not enough!
    

    在此之后,buf 应该已经填充了来自流的正确数量的数据,或者将引发 EOF。

    【讨论】:

    • 这不是java,按值抛出,不要使用new运算符
    • @Jiří 我希望你能对此进行扩展,因为是的:你应该在 C# 中使用 new(除非你重新抛出);我希望在这里看到的唯一常见更改是对该部分使用实用程序方法,而不是将throw 在同一方法中,以便throw 不会禁用内联(这会在异常中添加一个额外的堆栈帧,但是......嗯,你已经在抛出)。您是否在考虑 C/C++?
    • 是的,以为这是 CPP 没有注意到那个标志,对此感到抱歉 - 只是浏览一些 cpp 问题以找到解决方案并注意到这一点......
    【解决方案3】:

    从这里的答案中收集所有内容,我想出了以下解决方案。它依赖于源流长度。适用于 .NET 核心 3.1

    /// <summary>
    /// Copy stream based on source stream length
    /// </summary>
    /// <param name="source"></param>
    /// <param name="destination"></param>
    /// <param name="bufferSize">
    /// A value that is the largest multiple of 4096 and is still smaller than the LOH threshold (85K).
    /// So the buffer is likely to be collected at Gen0, and it offers a significant improvement in Copy performance.
    /// </param>
    /// <returns></returns>
    private async Task CopyStream(Stream source, Stream destination, int bufferSize = 81920)
    {
        var buffer = new byte[bufferSize];
        var offset = 0;
        while (offset < source.Length)
        {
            var leftToRead = source.Length - offset;
            var lengthToRead = leftToRead - buffer.Length < 0 ? (int)(leftToRead) : buffer.Length;
            var read = await source.ReadAsync(buffer, 0, lengthToRead).ConfigureAwait(false);
            if (read == 0)
                break;
            await destination.WriteAsync(buffer, 0, lengthToRead).ConfigureAwait(false);
            offset += read;
        }
        destination.Seek(0, SeekOrigin.Begin);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-09
      • 2018-06-28
      • 1970-01-01
      • 2019-09-13
      • 2020-06-12
      • 2020-10-20
      • 2015-09-01
      • 2011-12-21
      相关资源
      最近更新 更多