如何从流中读取n个字节?

时间:2011-09-22 11:36:27

标签: c# .net stream

这比我想象的要复杂一些。我正在尝试从流中读取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个字节,即使它们在流中可用。

3 个答案:

答案 0 :(得分:15)

稍微可读的版本:

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;
    }
}

答案 1 :(得分:12)

简单;你循环;

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。

答案 2 :(得分:1)

从这里的答案中汇总所有内容,我想出了以下解决方案。它依赖于源流长度。适用于 .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);
}