从流中创建字节数组

1093

如何从输入流中创建字节数组是首选的方法?

这是我目前使用 .NET 3.5 的解决方案。

Stream s;
byte[] b;

using (BinaryReader br = new BinaryReader(s))
{
    b = br.ReadBytes((int)s.Length);
}

阅读和写入流的块仍然是一个更好的主意吗?


69
当然,另一个问题是你是否应该从流中创建一个byte[]... 对于大数据,最好将流视为流! - Marc Gravell
4
实际上,您应该使用流而不是字节数组。但是有一些系统API不支持流。例如,您无法从流创建X509Certificate2,您必须提供一个字节数组(或字符串)。在这种情况下,由于x509证书可能不是大数据,所以这样做没问题。 - 0xced
二进制读取器(Binary Reader)不会将UTF-8编码附加到流中吗?如果您不是在读取文本(例如,如果您正在读取图像等),那么这会成为一个问题吗?https://learn.microsoft.com/en-us/dotnet/api/system.io.binaryreader.-ctor?view=net-5.0#System_IO_BinaryReader__ctor_System_IO_Stream_ - JMarsch
@JMarsch 我认为,只有在使用BinaryWriter/Reader写入/读取字符串时,UTF-8才会起作用。在您提供的链接中,他们将数字和字符串写入二进制文件。当写入字符串时,我相信首先写入长度,然后是UTF-8编码的字符串。但是,如果您只读取字节,则编码应该没有影响,因此回答您的问题,如果您正在读取图像或其他“实际”二进制数据,则不会出现此问题。 - nurchi
19个回答

1459

这真的取决于您是否可以信任s.Length。对于许多流,您不知道将有多少数据。在这种情况下(以及.NET 4之前),我会使用如下代码:

public static byte[] ReadFully(Stream input)
{
    byte[] buffer = new byte[16*1024];
    using (MemoryStream ms = new MemoryStream())
    {
        int read;
        while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
        {
            ms.Write(buffer, 0, read);
        }
        return ms.ToArray();
    }
}

使用.NET 4及以上版本,我会使用Stream.CopyTo,它与我的代码中的循环基本等效 - 创建MemoryStream,调用stream.CopyTo(ms),然后返回ms.ToArray()。工作完成。

也许我应该解释一下,为什么我的回答比其他回答更长。 Stream.Read不能保证它会读取所有请求的数据。例如,如果你从网络流中读取,它可能只读取一个数据包的内容并返回,即使很快就会有更多的数据到来。BinaryReader.Read将会一直读取到流的末尾或者达到指定的大小,但是你仍然需要知道开始时数据的大小。

上述方法将继续读取(并复制到MemoryStream中),直到它耗尽数据。然后请求MemoryStream返回数据的副本数组。如果你知道开始的大小——或者认为你知道开始的大小而不确定——那么可以构造MemoryStream以该大小开始。同样,你可以在最后加入一个检查,如果流的长度与缓冲区的大小相同(由MemoryStream.GetBuffer返回),那么可以直接返回缓冲区。因此,上述代码不完全优化,但至少是正确的。它不承担任何关闭流的责任——调用者应该负责。

请参见本文了解更多信息(以及替代实现)。


11
@Jon,值得一提的是http://www.yoda.arachsys.com/csharp/readbinary.html。 - Sam Saffron
7
如果你一直在写入流(stream)中,那么需要在读取之前“倒回”它。这里我们缺乏上下文,但流中只有一个指针用来标记当前位置,无论是读还是写都是用同一个。请注意不要改变原意,使翻译更加通俗易懂。 - Jon Skeet
6
@Jeff:这是呼叫者的责任。毕竟,流可能无法进行定位(例如网络流),或者根本没有必要倒带它。 - Jon Skeet
23
我可以问一下为什么特别是“16*1024”吗? - Anyname Donotcare
6
@just_name:我不知道这是否有任何意义,但是(16*1024)恰好是Int16.MaxValue的一半 :) - caesay
显示剩余9条评论

886

尽管Jon的答案是正确的,但他重新编写了已经存在于CopyTo中的代码。因此,在.Net 4中使用Sandip的解决方案,但在先前版本的.Net中使用Jon的答案。在许多情况下,CopyTo中的异常相当普遍,会使MemoryStream未被处理,因此可以通过使用"using"来改进Sandip的代码。

public static byte[] ReadFully(Stream input)
{
    using (MemoryStream ms = new MemoryStream())
    {
        input.CopyTo(ms);
        return ms.ToArray();
    }
}

7
你的答案和Jon的答案有什么不同之处?此外,我必须执行此输入.Position=0才能使CopyTo起作用。 - Jeff
2
@nathan,从Web客户端读取文件(文件大小为1MB)- IIS将不得不将整个1MB加载到内存中,对吗? - Royi Namir
10
@Jeff,我的回答只适用于.NET 4或以上版本。Jons的方法通过重写后续版本中提供给我们的功能可以在较低版本上运行。如果你有一个可定位的流,想要从开头复制,那么你可以使用你的代码或input.Seek(0, SeekOrigin.Begin)将位置移动到开头,但在许多情况下,你的流可能不可定位。 - Nathan Phillips
5
检查一下 input 是否已经是一个 MemoryStream 并进行短路处理可能是值得的。我知道调用者传递一个 MemoryStream 是愚蠢的,但是... - Jodrell
3
@Jodrell,没错。如果你在将数百万个小型流复制到内存中,并且其中一个是“MemoryStream”,那么优化是否有意义取决于进行数百万次类型转换所需的时间与将其中一个“MemoryStream”复制到另一个“MemoryStream”的时间之间的比较。 - Nathan Phillips
显示剩余7条评论

133

只想指出如果您已经有一个MemoryStream,您可以使用memorystream.ToArray()。

此外,如果您正在处理未知或不同子类型的流,并且您可以收到MemoryStream,则可以为这些情况使用上述方法,并仍然使用接受的答案来处理其他情况,如下所示:

public static byte[] StreamToByteArray(Stream stream)
{
    if (stream is MemoryStream)
    {
        return ((MemoryStream)stream).ToArray();                
    }
    else
    {
        // Jon Skeet's accepted answer 
        return ReadFully(stream);
    }
}

1
哎呀,这些点赞是为了什么?即使做出最慷慨的假设,这个只适用于已经是MemoryStream的流。当然,这个例子显然是不完整的,因为它使用了一个未初始化的变量。 - Roman Starkov
3
没错,谢谢您指出这一点。不过对于MemoryStream来说,重点仍然存在,所以我已经修复了内容以反映这一点。 - Fernando Neira
只是提一下,对于MemoryStream,另一个可能性是MemoryStream.GetBuffer(),尽管其中存在一些陷阱。请参见https://dev59.com/uHI-5IYBdhLWcg3w18V3和http://krishnabhargav.blogspot.dk/2009/06/net-funda-memorystream-toarray-vs.html。 - RenniePet
5
这实际上会在Skeet的代码中引入一个bug;如果在调用readfully之前调用stream.Seek(1L, SeekOrigin.Begin),如果该流是内存流,则读取到的字节数将比其他流多1个字节。如果调用方期望从当前位置读取到流的末尾,则不应使用CopyToToArray();在大多数情况下这不会成为问题,但如果调用方不知道这种古怪的行为,他们会感到困惑。 - leat

76
MemoryStream ms = new MemoryStream();
file.PostedFile.InputStream.CopyTo(ms);
var byts = ms.ToArray();
ms.Dispose();

15
为避免内存碎片化,应使用“new MemoryStream(file.PostedFile.ContentLength)”创建MemoryStream。 - Dan Randolph

57

这只是我的一些个人看法... 我经常使用的做法是将方法组织成自定义助手函数。

public static class StreamHelpers
{
    public static byte[] ReadFully(this Stream input)
    {
        using (MemoryStream ms = new MemoryStream())
        {
            input.CopyTo(ms);
            return ms.ToArray();
        }
    }
}

在配置文件中添加命名空间,并在任何需要的地方使用它。


7
请注意,这将无法在 .NET 3.5 及以下版本中运行,因为在 4.0 版本之前的 Stream 上不存在 CopyTo 方法。 - Tim

21

你可以简单地使用MemoryStream类的ToArray()方法,例如-

MemoryStream ms = (MemoryStream)dataInStream;
byte[] imageBytes = ms.ToArray();

4
只有当dataInStream已经是一个MemoryStream时,这才起作用。 - Fowl
@Fowl,你可以执行dataInStream.CopyTo(ms);,但请确保在尝试将dataInStream复制到ms之前先执行MemoryStream ms = MemoryStream(); - Joseph

14

你甚至可以通过扩展使它变得更加花哨:

namespace Foo
{
    public static class Extensions
    {
        public static byte[] ToByteArray(this Stream stream)
        {
            using (stream)
            {
                using (MemoryStream memStream = new MemoryStream())
                {
                     stream.CopyTo(memStream);
                     return memStream.ToArray();
                }
            }
        }
    }
}

然后像普通方法一样调用它:

byte[] arr = someStream.ToByteArray()

79
我认为把输入流放在 using 块中是个不好的主意。这个责任应该由调用过程承担。 - Jeff

8

在Bob的代码中(即提问者的代码),我遇到了编译时错误。Stream.Length是long类型,而BinaryReader.ReadBytes需要一个整数参数。在我的情况下,我不希望处理需要长精度的流,因此我使用以下代码:

Stream s;
byte[] b;

if (s.Length > int.MaxValue) {
  throw new Exception("This stream is larger than the conversion algorithm can currently handle.");
}

using (var br = new BinaryReader(s)) {
  b = br.ReadBytes((int)s.Length);
}

8

如果有人喜欢,这里有一个仅适用于.NET 4+的解决方案,可以形成扩展方法而不需要在MemoryStream上进行不必要的Dispose调用。 这是一个无望的微小优化,但值得注意的是,未处理MemoryStream的Dispose并不是一个真正的失败。

public static class StreamHelpers
{
    public static byte[] ReadFully(this Stream input)
    {
        var ms = new MemoryStream();
        input.CopyTo(ms);
        return ms.ToArray();
    }
}

8

将两个最受欢迎的答案组合成扩展方法:

public static byte[] ToByteArray(this Stream stream)
{
    if (stream is MemoryStream)
        return ((MemoryStream)stream).ToArray();
    else
    {
        using MemoryStream ms = new();
        stream.CopyTo(ms);
        return ms.ToArray();
    }            
}

当您添加代码时,也请简要描述您提出的解决方案。 - yakobom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接