从流创建字节数组


911

从输入流创建字节数组的首选方法是什么?

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

Stream s;
byte[] b;

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

读写流的块还是更好的主意吗?


60
当然,另一个问题是您是否应该从流中创建一个byte [] ...对于大数据,最好将流视为流!
马克·格拉韦尔

2
实际上,您可能应该使用流而不是byte []。但是有些系统API不支持流。例如,您不能从流创建X509Certificate2,而必须给它提供一个byte [](或字符串)。在这种情况下,因为x509证书可能不是大数据,所以很好。
0xced

Answers:


1292

这实际上取决于您是否可以信任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),则可以仅返回缓冲区。因此,上面的代码没有得到最优化,但至少是正确的。它不承担关闭流的任何责任-调用者应该这样做。

请参阅本文以获取更多信息(和替代实现)。



6
@Jeff:我们这里确实没有上下文,但是如果您一直在写流,那么是的,您需要在阅读之前“倒带”它。只有一个“光标”说明您在流中的位置-不是一个用于阅读,一个用于书写。
乔恩·斯基特

5
@Jeff:这是来电者的责任。毕竟,该流可能是不可搜索的(例如,网络流),或者可能根本不需要倒带。
乔恩·斯基特

18
我能问为什么16*1024特别吗?
Anyname Donotcare

5
@just_name:我不知道这是否有意义,但是(16 * 1024)恰好是Int16.MaxValue的一半:)
caesay 2012年

733

虽然乔恩的答案是正确的,但他正在重写中已经存在的代码CopyTo。因此,对于.Net 4,请使用Sandip解决方案,而对于.Net的早期版本,请使用Jon的答案。Sandip的代码将通过使用“ using”进行改进,因为CopyTo在许多情况下,出现异常的可能性很大,并且MemoryStream不会处理掉。

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

6
你的答案和乔恩的答案有什么不同?我也必须执行此input.Position = 0才能使CopyTo工作。
杰夫

1
@nathan,从Web客户端读取文件(filizesize = 1mb)-IIS将必须将整个1mb加载到其内存中,对吗?
罗伊·纳米尔

5
@Jeff,我的答案仅适用于.Net 4或更高版本,Jons将通过重写在更高版本中提供给我们的功能来在较低版本上工作。您是正确的,CopyTo只会从当前位置复制,如果您有Seekable流,并且想要从头开始复制,则可以使用代码或input.Seek(0,SeekOrigin.Begin)移到头开始,尽管在很多情况下,您的视频流可能无法搜索。
内森·菲利普斯

5
可能值得检查是否input已经MemorySteam短路。我知道通过呼叫者MemoryStream但通过呼叫者是愚蠢的……
Jodrell

3
@Jodrell,是这样。如果复制数百万小溪流到内存中,其中一人是MemoryStream那么的优化是否有道理在您的上下文是做数以百万计的类型转换的反对所花费的时间所花费的时间复制一个,这是一个比较MemoryStream成另一个MemoryStream
内森·菲利普斯

114

只是要指出,以防万一您已经拥有一个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
呵呵,所有支持的目的是什么?即使有最慷慨的假设,这也仅适用于已经为MemoryStreams的流。当然,该示例在使用未初始化变量方面也显然是不完整的。
罗曼·斯塔科夫

3
是的,谢谢你指出这一点。重点仍然代表MemoryStream,因此我将其固定以反映这一点。
Fernando Neira

只需提及MemoryStream的另一种可能性是MemoryStream.GetBuffer(),尽管其中涉及一些陷阱。见stackoverflow.com/questions/1646193/...krishnabhargav.blogspot.dk/2009/06/...
RenniePet

4
这实际上在Skeet的代码中引入了一个错误。如果您调用stream.Seek(1L, SeekOrigin.Begin),则在您进行可读调用之前,如果该流是内存流,则比任何其他流多1个字节。如果调用者希望从当前位置读取到流的末尾,则不得使用CopyToToArray(); 在大多数情况下,这不是问题,但是如果呼叫者不知道这种古怪的行为,则会感到困惑。
真皮休闲

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

9
应该使用“新的MemoryStream(file.PostedFile.ContentLength)”创建MemoryStream,以避免内存碎片。
Dan Randolph

52

只是我的几分钱...我经常使用的做法是将这样的方法组织为自定义帮助程序

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

将名称空间添加到配置文件中,并在您希望的任何地方使用它


5
请注意,此功能在.NET 3.5及以下版本中将CopyTo无法使用,Stream直到4.0 才可用。
蒂姆(Tim)


10

您甚至可以通过扩展使其更加出色:

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()

67
我认为将输入流放在using块中是个坏主意。该责任应由调用程序承担。
杰夫,

7

我收到鲍勃(即提问者)的代码的编译时错误。Stream.Length是一个长整数,而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);
}

5

如果有人喜欢它,这是一个仅.NET 4+解决方案,形成为扩展方法,而无需在MemoryStream上进行不必要的Dispose调用。这是一个毫无希望的琐碎优化,但是值得注意的是,处理内存流失败并不是真正的失败。

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

3

上面的一个是可以的...但是当您通过SMTP发送邮件时(如果需要),您将遇到数据损坏的情况。我已更改为其他有助于正确发送字节对字节的内容:'

using System;
using System.IO;

        private static byte[] ReadFully(string input)
        {
            FileStream sourceFile = new FileStream(input, FileMode.Open); //Open streamer
            BinaryReader binReader = new BinaryReader(sourceFile);
            byte[] output = new byte[sourceFile.Length]; //create byte array of size file
            for (long i = 0; i < sourceFile.Length; i++)
                output[i] = binReader.ReadByte(); //read until done
            sourceFile.Close(); //dispose streamer
            binReader.Close(); //dispose reader
            return output;
        }'

我看不到此代码可避免数据损坏。你能解释一下吗?
Nippey 2012年

假设您有一张图片,并且想通过SMTP发送。您可能会使用base64编码。由于某种原因,如果将文件拆分为字节,则文件将损坏。但是,使用二进制读取器将允许文件成功发送。
NothinRandom

3
有点老了,但是我觉得有必要提及-实现@NothinRandom提供的是字符串而不是流。不过,在这种情况下,仅使用File.ReadAllBytes可能是最简单的。
XwipeoutX 2014年

1
由于危险的代码风格而拒绝投票(不能自动处置/使用)。
arni

可悲的是,只允许-1,与问题无关,文件名参数命名为input,不进行处理,没有读取缓冲区,没有文件模式,并且二进制读取器为什么逐字节读取?
阿里达内·阿拉莫(AridaneÁlamo),

2

创建一个帮助器类,并在您想使用它的任何地方引用它。

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

2

在名称空间RestSharp.Extensions中,有方法ReadAsBytes。在此方法内部使用MemoryStream,并且有与该页面上某些示例中相同的代码,但是当您使用RestSharp时,这是最简单的方法。

using RestSharp.Extensions;
var byteArray = inputStream.ReadAsBytes();

1

您可以使用此扩展方法。

public static class StreamExtensions
{
    public static byte[] ToByteArray(this Stream stream)
    {
        var bytes = new List<byte>();

        int b;
        while ((b = stream.ReadByte()) != -1)
            bytes.Add((byte)b);

        return bytes.ToArray();
    }
}

1

这是我正在使用,测试和运行良好的功能。请记住,“ input”不应为null,“ input.position”应在读取前重置为“ 0”,否则将中断读取循环,并且不会读取任何内容以转换为数组。

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

            return temp;
        }
    }

-1
public static byte[] ToByteArray(Stream stream)
    {
        if (stream is MemoryStream)
        {
            return ((MemoryStream)stream).ToArray();
        }
        else
        {
            byte[] buffer = new byte[16 * 1024];
            using (MemoryStream ms = new MemoryStream())
            {
                int read;
                while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    ms.Write(buffer, 0, read);
                }
                return ms.ToArray();
            }
        }            
    }

您只是从#1和#3答案中复制了代码,而没有添加任何有价值的东西。请不要那样做。:)
CodeCaster

添加代码时,还请简短描述建议的解决方案。
yakobom '17

-5

我能够使它在一行上工作:

byte [] byteArr= ((MemoryStream)localStream).ToArray();

johnnyRose所阐明的,以上代码仅适用于MemoryStream


2
如果localStream不是,该MemoryStream怎么办?此代码将失败。
johnnyRose

localStream必须是基于流的对象。有关基于流的对象的更多信息,请点击这里stackoverflow.com/questions/8156896/…–
Abba

1
我是想建议的是,如果你试图投localStreamMemoryStream,但是localStream不是一个MemoryStream,它就会失败。这段代码可以很好地编译,但是根据的实际类型,它可能会在运行时失败localStream。您不能总是将基本类型任意转换为子类型。在这里阅读更多这是另一个很好的例子,它说明了为什么不能总是这样做。
johnnyRose

详细说明我的上述评论:所有MemoryStreams都是Streams,但并非所有Streams都是MemoryStreams。
johnnyRose

所有基于Stream的对象都将Stream作为基本类型。并且Stream本身始终可以转换为内存流。无论您尝试将哪种基于流的对象强制转换为Meomry Stream,它都应始终有效。我们的目标是将流对象转换为字节数组。你能给我一个失败的案例吗?
阿巴
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.