C# 如何从流中准确读取n个字节?

C# 如何从流中准确读取n个字节?,c#,.net,stream,C#,.net,Stream,这比我最初想象的要复杂一些。我正在尝试从一个流中读取n个字节 读取不必返回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; 我希望确切的sizebytes

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

读取不必返回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
bytes,但是按照规范,FileStream也可以返回大量的1字节块。这必须避免

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

我也看了一下,但它的规范也没有明确说明n字节肯定会返回

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

简单;你循环

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。

一个稍微可读的版本:

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

根据这里的答案,我得出了以下解决方案。它依赖于源流的长度。适用于.NETCore3.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);
}
//
///基于源流长度复制流
/// 
/// 
/// 
/// 
///是4096的最大倍数,并且仍然小于LOH阈值(85K)。
///因此,缓冲区很可能在Gen0收集,并且它提供了拷贝性能的显著改进。
/// 
/// 
专用异步任务CopyStream(流源、流目标、int-bufferSize=81920)
{
var buffer=新字节[bufferSize];
var偏移=0;
while(偏移量<源长度)
{
var leftToRead=source.Length-offset;
var lengthToRead=leftoread-buffer.Length<0?(int)(leftoread):buffer.Length;
var read=await source.ReadAsync(缓冲区,0,lengthToRead).ConfigureAwait(false);
如果(读==0)
打破
wait destination.WriteAsync(缓冲区,0,lengthToRead)。configureWait(false);
偏移量+=读取;
}
destination.Seek(0,SeekOrigin.Begin);
}

BinaryReader.ReadBytes(int)返回请求的字节数;如果流提前结束,它将返回它在该点之前读取的内容(因此比请求的要少)。@bosonix这会很方便。您有此信息的来源吗?这是在MSDN页面中指定的,我还查看了反汇编代码。@bosonix我明白了。这是相当明确的,如果代码匹配,这似乎是最好的解决方案。我很困惑为什么我没有注意到这个方法(显然在我问这个问题的时候它是可用的),甚至连Marc Gravell都没有建议它。这不是java,按值抛出,不要使用
new
operator@Jiří我希望您能对此进行详细介绍,因为是的:在输入C#时,您应该使用
new
(除非您正在抛出);我希望在这里看到的唯一常见更改是对该部分使用实用程序方法,而不是在相同的方法中使用
抛出
,这样
抛出
就不会禁用内联(这会在异常中添加额外的堆栈帧,但是……嗯,您已经抛出了).你可能在想C/C++?是的,我以为这是CPP没有注意到那个标志,很抱歉-只是浏览了一些CPP问题以找到解决方案,并注意到了这个。。。
/// <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);
}