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

下面是我目前使用。net 3.5的解决方案。

Stream s;
byte[] b;

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

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


当前回答

如果流支持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;
}

根据流是否支持搜索在两个版本之间切换的完整代码。它包括检查位置和不可靠的长度。这可能会略微降低速度。在我的测试中,ToArrayAsyncDirect比ToArrayAsyncGeneral快3倍。

public static class StreamExtensions
{
    public static readonly byte[] TempArray = new byte[4];

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

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

    /// <summary>
    /// Converts stream to byte array through MemoryStream. This doubles allocations compared to ToArrayAsyncDirect.
    /// </summary>
    /// <param name="stream">Stream</param>
    /// <param name="cancellationToken">Cancellation token</param>
    /// <returns></returns>
    private static async Task<byte[]> ToArrayAsyncGeneral(Stream stream, CancellationToken cancellationToken)
    {
        using MemoryStream memoryStream = new MemoryStream();
        await stream.CopyToAsync(memoryStream, cancellationToken);
        return memoryStream.ToArray();
    }

    /// <summary>
    /// Converts stream to byte array without unnecessary allocations.
    /// </summary>
    /// <param name="stream">Stream</param>
    /// <param name="cancellationToken">Cancellation token</param>
    /// <returns>Stream data as array</returns>
    /// <exception cref="ArgumentException">Thrown if stream is not providing correct Length</exception>
    private static async Task<byte[]> ToArrayAsyncDirect(Stream stream, CancellationToken cancellationToken)
    {
        if (stream.Position > 0)
        {
            throw new ArgumentException("Stream is not at the start!");
        }


        var array = new byte[stream.Length];
        int bytesRead = await stream.ReadAsync(array, 0, (int)stream.Length, cancellationToken);

        if (bytesRead != array.Length ||
            await stream.ReadAsync(TempArray, 0, TempArray.Length, cancellationToken) > 0)
        {
            throw new ArgumentException("Stream does not have reliable Length!");
        }

        return array;
    }
}

其他回答

如果有人喜欢它,这里有一个。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();
    }
}

如果流支持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;
}

根据流是否支持搜索在两个版本之间切换的完整代码。它包括检查位置和不可靠的长度。这可能会略微降低速度。在我的测试中,ToArrayAsyncDirect比ToArrayAsyncGeneral快3倍。

public static class StreamExtensions
{
    public static readonly byte[] TempArray = new byte[4];

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

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

    /// <summary>
    /// Converts stream to byte array through MemoryStream. This doubles allocations compared to ToArrayAsyncDirect.
    /// </summary>
    /// <param name="stream">Stream</param>
    /// <param name="cancellationToken">Cancellation token</param>
    /// <returns></returns>
    private static async Task<byte[]> ToArrayAsyncGeneral(Stream stream, CancellationToken cancellationToken)
    {
        using MemoryStream memoryStream = new MemoryStream();
        await stream.CopyToAsync(memoryStream, cancellationToken);
        return memoryStream.ToArray();
    }

    /// <summary>
    /// Converts stream to byte array without unnecessary allocations.
    /// </summary>
    /// <param name="stream">Stream</param>
    /// <param name="cancellationToken">Cancellation token</param>
    /// <returns>Stream data as array</returns>
    /// <exception cref="ArgumentException">Thrown if stream is not providing correct Length</exception>
    private static async Task<byte[]> ToArrayAsyncDirect(Stream stream, CancellationToken cancellationToken)
    {
        if (stream.Position > 0)
        {
            throw new ArgumentException("Stream is not at the start!");
        }


        var array = new byte[stream.Length];
        int bytesRead = await stream.ReadAsync(array, 0, (int)stream.Length, cancellationToken);

        if (bytesRead != array.Length ||
            await stream.ReadAsync(TempArray, 0, TempArray.Length, cancellationToken) > 0)
        {
            throw new ArgumentException("Stream does not have reliable Length!");
        }

        return array;
    }
}

上面这个还可以…但是当你通过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;
        }'

虽然Jon的回答是正确的,但他正在重写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();
    }
}

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

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

这种优化是基于ToArray的源代码调用一些内部方法的事实。