Создание байтового массива из потока

Каков предпочтительный метод для создания байтового массива из входного потока?

Вот мое текущее решение с.NET 3.5.

Stream s;
byte[] b;

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

Это все-таки лучшая идея для чтения и записи фрагментов потока?

19 ответов

Решение

Это действительно зависит от того, можете ли вы доверять 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) тогда вы можете просто вернуть буфер. Таким образом, приведенный выше код не совсем оптимизирован, но, по крайней мере, будет правильным. Он не несет никакой ответственности за закрытие потока - вызывающий должен сделать это.

Смотрите эту статью для получения дополнительной информации (и альтернативной реализации).

Хотя ответ Джона верен, он переписывает код, который уже существует в CopyTo. Поэтому для.Net 4 используйте решение Sandip, а для предыдущей версии.Net используйте ответ Джона. Код Сандипа будет улучшен путем использования "использования", поскольку исключения в CopyTo, во многих ситуациях, весьма вероятны и оставят MemoryStream не утилизированным.

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

Просто хочу отметить, что если у вас есть 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 ms = new MemoryStream();
file.PostedFile.InputStream.CopyTo(ms);
var byts = ms.ToArray();
ms.Dispose();

Только мои пару центов... практика, которую я часто использую, состоит в том, чтобы организовать методы как это как пользовательский помощник

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

добавить пространство имен в файл конфигурации и использовать его где угодно

Вы можете просто использовать метод ToArray() класса MemoryStream, например,

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

Вы даже можете сделать его более привлекательным с помощью расширений:

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

Я получаю ошибку во время компиляции с кодом Боба (т.е. спрашивающего). 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);
}
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();
            }
        }            
    }

На случай, если кому-то это понравится, вот решение.NET 4+, созданное как метод расширения без ненужного вызова Dispose для MemoryStream. Это безнадежно тривиальная оптимизация, но стоит отметить, что неудача в утилизации MemoryStream не является реальной ошибкой.

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

С приведенным выше кодом все в порядке... но вы столкнетесь с повреждением данных при отправке данных по 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;
        }'

Если поток поддерживает свойство Length, байтовый массив может быть создан непосредственно из него. Преимущество в том, что создает массив дважды, плюс, возможно, несколько неиспользованных лишних байтов в буфере. Это решение выделяет точный необходимый массив. Если поток не поддерживает свойство Length, он выдаст исключение.

Также стоит отметить, что массивы не могут быть больше int.MaxValue.

      public static async Task<byte[]> ToArrayAsync(this Stream stream)
    {
        if (stream.Length > int.MaxValue)
        {
            throw new ArgumentOutOfRangeException("Cannot convert stream larger than max value of signed integer (2 147 483 647) to array");
        }

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

Это функция, которую я использую, протестировал и хорошо работал. пожалуйста, имейте в виду, что "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;
        }
    }

Создайте вспомогательный класс и ссылайтесь на него везде, где вы хотите его использовать.

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

В пространстве имен RestSharp.Extensions есть метод ReadAsBytes. Внутри этого метода используется MemoryStream, и на этой странице есть тот же код, что и в некоторых примерах, но когда вы используете RestSharp, это самый простой способ.

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

Поскольку современной (т.е. асинхронной) версии этого ответа не существует, я использую для этой цели метод расширения:

      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вызывает некоторые внутренние методы.

Вы можете использовать этот метод расширения.

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();
    }
}
      MemoryStream stream = new MemoryStream();
// do what you want to save in stream buffer
//  ...
// then define byte array with specific size same as stream length.
byte[] readByte = new byte[stream.Length];
// copy all byte from stream to an byte array
readByte = stream.ToArray();

Я смог заставить его работать в одной строке:

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

как пояснил johnnyRose, johnnyRose код будет работать только для MemoryStream

Другие вопросы по тегам