ChatGPT解决这个技术问题 Extra ChatGPT

从流中创建字节数组

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

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

Stream s;
byte[] b;

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

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

当然,另一个问题是您是否应该从流中创建 byte[]... 对于大数据,最好将流视为流!
实际上,您可能应该使用流而不是字节 []。但是有些系统 API 不支持流。例如,您不能从流中创建 X509Certificate2,您必须给它一个 byte[](或字符串)。在这种情况下,这很好,因为 x509 证书可能不是大数据
二进制阅读器不会将 UTF-8 编码附加到流中吗?如果您不阅读文本(例如,如果您正在阅读图像等),那不是问题吗? docs.microsoft.com/en-us/dotnet/api/…

J
Jon Skeet

这实际上取决于您是否可以信任 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 返回),那么您可以只返回缓冲区。所以上面的代码不是很优化,但至少是正确的。它不承担关闭流的任何责任——调用者应该这样做。

有关详细信息(以及替代实现),请参阅 this article


@Jon,值得一提的是yoda.arachsys.com/csharp/readbinary.html
@Jeff:我们在这里并没有真正的上下文,但是如果您一直在写入流,那么是的,您需要在阅读之前“倒带”它。只有一个“光标”表示您在流中的位置 - 不是一个用于阅读,一个用于写入。
@Jeff:这是调用者的责任。毕竟,流可能是不可搜索的(例如网络流),或者根本不需要倒带。
我能问一下为什么是16*1024吗?
@just_name:我不知道这是否有任何意义,但 (16*1024) 恰好是 Int16.MaxValue 的一半 :)
N
Nursnaaz

虽然 Jon 的回答是正确的,但他正在重写 CopyTo 中已经存在的代码。所以对于 .Net 4 使用 Sandip 的解决方案,但对于以前版本的 .Net 使用 Jon 的答案。 Sandip 的代码可以通过使用“使用”来改进,因为在许多情况下,CopyTo 中的异常很可能会导致 MemoryStream 不被处理。

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

你的答案和乔恩的有什么不同?我也必须这样做 input.Position=0 才能使 CopyTo 工作。
@nathan,从网络客户端读取一个文件(filizesize=1mb)-iis 必须将整个 1mb 加载到它的内存中,对吗?
@Jeff,我的回答仅适用于 .Net 4 或更高版本,Jons 将通过重写在更高版本中提供给我们的功能来处理较低版本。你是正确的,CopyTo 只会从当前位置复制,如果你有一个 Seekable 流并且你想从头开始复制,那么你可以使用你的代码或 input.Seek(0, SeekOrigin.Begin) 移动到开头,尽管在许多情况下,您的流可能不可搜索。
如果 input 已经是 MemorySteam 并且短路,可能值得检查。我知道调用者传递 MemoryStream 会很愚蠢,但是......
@Jodrell,正是如此。如果您将数百万个小流复制到内存中,其中一个是 MemoryStream,那么优化在您的上下文中是否有意义是进行数百万次类型转换所花费的时间与复制一个所花费的时间的比较这是一个 MemoryStream 到另一个 MemoryStream
m
meJustAndrew

只是想指出,如果您有一个 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);
    }
}

呵呵,大家点赞是为了什么?即使有最宽泛的假设,这也仅适用于已经是 MemoryStream 的流。当然,这个例子显然也是不完整的,它是如何使用一个未初始化的变量的。
没错,谢谢指出。不过,这一点仍然代表 MemoryStream,因此我对其进行了修复以反映这一点。
只需提到 MemoryStream 的另一种可能性是 MemoryStream.GetBuffer(),尽管其中涉及一些问题。请参阅 stackoverflow.com/questions/1646193/…krishnabhargav.blogspot.dk/2009/06/…
这实际上在 Skeet 的代码中引入了一个错误;如果您调用 stream.Seek(1L, SeekOrigin.Begin),在您充分调用之前,如果流是内存流,您将比任何其他流多获得 1 个字节。如果调用者希望从当前位置读取到流的末尾,那么您不能使用 CopyToToArray();在大多数情况下,这不是问题,但如果调用者不知道这种古怪的行为,他们会感到困惑。
S
Sandip Patel
MemoryStream ms = new MemoryStream();
file.PostedFile.InputStream.CopyTo(ms);
var byts = ms.ToArray();
ms.Dispose();

MemoryStream 应使用“new MemoryStream(file.PostedFile.ContentLength)”创建以避免内存碎片。
M
Mr. Pumpkin

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

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

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


请注意,这在 .NET 3.5 及更低版本中不起作用,因为 CopyTo 在 4.0 之前在 Stream 上不可用。
N
Nilesh Kumar

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

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

这仅在 dataInStream 已经是 MemoryStream 时才有效
M
Michal T

你甚至可以通过扩展使它更漂亮:

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

我认为将输入流放在 using 块中是个坏主意。该责任应由调用过程负责。
B
Brian Hinchey

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

S
SensorSmith

如果有人喜欢它,这里是一个仅 .NET 4+ 的解决方案,它作为扩展方法形成,无需对 MemoryStream 进行不必要的 Dispose 调用。这是一个无可救药的微不足道的优化,但值得注意的是,未能 Dispose 一个 MemoryStream 并不是真正的失败。

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

N
NothinRandom

上面的那个没问题...但是当您通过 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;
        }'

我看不出这段代码在哪里可以避免数据损坏。你能解释一下吗?
假设您有一张图片,并且您想通过 SMTP 发送它。您可能会使用 base64 编码。出于某种原因,如果将文件分解为字节,文件就会损坏。但是,使用二进制阅读器将允许成功发送文件。
有点旧,但我觉得值得一提 - @NothinRandom 提供的实现适用于字符串,而不是流。不过,在这种情况下,使用 File.ReadAllBytes 可能是最简单的。
由于危险的代码风格(没有自动处置/使用)而投反对票。
遗憾的是只允许-1,与问题无关,文件名参数命名为输入,不处理,没有读取缓冲区,没有文件模式,二进制阅读器逐字节读取为什么?
D
Draken

创建一个辅助类并在您希望使用它的任何地方引用它。

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

W
Wieslaw Olborski

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

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

F
Fred.S

这是我正在使用、测试和运行良好的功能。请记住,'input' 不应该为空,'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;
        }
    }

a
adsamcik

如果流支持 Length 属性,则可以直接创建字节数组。优点是 MemoryStream.ToArray 创建了两次数组。另外,缓冲区中可能有一些未使用的额外字节。此解决方案分配所需的确切数组。如果流不支持 Length 属性,则会抛出 NotSupportedException 异常。

还值得注意的是,数组不能大于 int.MaxValue。

public static async Task<byte[]> ToArrayAsync(this Stream stream)
{
    var array = new byte[stream.Length];
    await stream.ReadAsync(array, 0, (int)stream.Length);
    return array;
}

根据流是否支持查找在两个版本之间切换的完整代码。

/// <summary>
/// Converts stream to byte array.
/// </summary>
/// <param name="stream">Stream</param>
/// <returns>Binary data from stream in an array</returns>
public static async Task<byte[]> ToArrayAsync(this Stream stream)
{
    if (!stream.CanRead)
    {
        throw new AccessViolationException("Stream cannot be read");
    }

    if (stream.CanSeek)
    {
        return await ToArrayAsyncDirect(stream);
    }
    else
    {
        return await ToArrayAsyncGeneral(stream);
    }
}

private static async Task<byte[]> ToArrayAsyncGeneral(Stream stream)
{
    using (var memoryStream = new MemoryStream())
    {
        await stream.CopyToAsync(memoryStream);
        return memoryStream.ToArray();
    }
}

private static async Task<byte[]> ToArrayAsyncDirect(Stream stream)
{
    var array = new byte[stream.Length];
    await stream.ReadAsync(array, 0, (int)stream.Length);
    return array;
}

O
Orace

您可以使用此扩展方法。

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

        int b;

        // -1 is a special value that mark the end of the stream
        while ((b = stream.ReadByte()) != -1)
            bytes.Add((byte)b);

        return bytes.ToArray();
    }
}

K
Kirk Woll

由于此答案没有现代(即异步)版本,因此这是我为此目的使用的扩展方法:

public static async Task<byte[]> ReadAsByteArrayAsync(this Stream source)
{
    // Optimization
    if (source is MemoryStream memorySource)
        return memorySource.ToArray();

    using var memoryStream = new MemoryStream();
    await source.CopyToAsync(memoryStream);
    return memoryStream.ToArray();
}

优化基于 source code for ToArray 调用一些内部方法这一事实。


M
Mikael Dúi Bolinder

将两个投票最多的答案组合成一个扩展方法:

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

添加代码时,还要简短地描述您提出的解决方案。
C
Community

我能够让它在一条线上工作:

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

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


如果 localStream 不是 MemoryStream 怎么办?此代码将失败。
localStream 必须是基于流的对象。更多关于基于流的对象在这里stackoverflow.com/questions/8156896/…
我想建议的是,如果您尝试将 localStream 转换为 MemoryStream,但 localStream 不是 MemoryStream,它失败.此代码可以正常编译,但在运行时可能会失败,具体取决于 localStream 的实际类型。您不能总是随意地将基类型转换为子类型; read more hereThis is another good example 解释了为什么您不能总是这样做。
详细说明我的上述评论:所有 MemoryStreams 都是 Streams,但并非所有 Streams 都是 MemoryStreams。
那是错误的。简单示例:无法将 FileStream 强制转换为 MemoryStream,并且将失败并出现以下错误:“无法将 'System.IO.FileStream' 类型的对象强制转换为 'System.IO.MemoryStream' 类型。”示例:using (Stream fs = new FileStream(@"C:\pathtofile.txt", FileMode.Open)) { var memoryStream = (MemoryStream)fs; } 如果您只使用 var,它将无法编译,因为它会隐式键入 MemoryStream。如前所述,使用上面的 Stream 键入它会创建一个运行时异常。试试看,自己看看。