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


913

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

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

Stream s;
byte[] b;

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

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


60
Конечно, другой вопрос заключается в том, следует ли создавать байт [] из потока ... для больших данных желательно рассматривать поток как поток!
Марк Гравелл

2
В самом деле, вы, вероятно, должны использовать поток вместо байта []. Но есть некоторые системные API, которые не поддерживают потоки. Например, вы не можете создать X509Certificate2 из потока, вы должны дать ему байт [] (или строку). В этом случае это нормально, поскольку сертификат x509, вероятно, не большие данные .
0xced

Ответы:


1295

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

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


9
@Jon, возможно, стоит упомянуть yoda.arachsys.com/csharp/readbinary.html
Сэм Шафран,

6
@Jeff: у нас на самом деле нет контекста, но если вы писали в поток, то да, вам нужно «перемотать» его перед чтением. Есть только один «курсор», говорящий о том, где вы находитесь в потоке - не один для чтения, а отдельный для записи.
Джон Скит

5
@Jeff: это ответственность звонящего. В конце концов, поток может быть недоступен для поиска (например, сетевой поток), или может просто не быть необходимости перематывать его.
Джон Скит

18
Могу я спросить, почему 16*1024конкретно?
Anyname Donotcare

5
@just_name: я не знаю, имеет ли это какое-либо значение, но (16 * 1024) оказывается половиной Int16.MaxValue :)
caesay

735

Хотя ответ Джона верен, он переписывает код, который уже существует в CopyTo. Так что для .Net 4 используйте решение Sandip, но для предыдущей версии .Net используйте ответ Джона. Код Сандипа будет улучшен путем использования «использования» в качестве исключений, 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, прочитайте файл из веб-клиента (filizesize = 1mb) - iis должен будет загрузить целые 1mb в свою память, верно?
Рой Намир

5
@Jeff, мой ответ будет работать только на .Net 4 или выше, Jons будет работать на более низких версиях, переписав функциональность, предоставленную нам в более поздней версии. Вы правы, что CopyTo будет копировать только с текущей позиции, если у вас есть поток Seekable и вы хотите копировать с начала, то вы можете перейти к началу, используя свой код или input.Seek (0, SeekOrigin.Begin), хотя во многих случаях ваш поток может быть недоступен для поиска.
Натан Филлипс

5
Возможно, стоит проверить, если inputэто уже MemorySteamи короткое замыкание. Я знаю, что было бы глупо, чтобы вызывающий игрок прошел, MemoryStreamно ...
Джодрелл

3
@Jodrell, именно так. Если вы копируете миллионы небольших потоков в память, и один из них - это MemoryStreamто, имеет ли смысл оптимизация в вашем контексте, это сравнение времени, затраченного на миллионы преобразований типов, с временем, затрачиваемым на копирование того, который находится MemoryStreamв другой MemoryStream.
Натан Филлипс

114

Просто хочу указать, что в случае, если у вас есть MemoryStream, у вас уже есть memorystream.ToArray()для этого.

Кроме того, если вы имеете дело с потоками неизвестных или разных подтипов и можете получить a 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, поэтому я исправил это, чтобы отразить это.
Фернандо Нейра

Просто отметим, что для MemoryStream есть еще одна возможность - MemoryStream.GetBuffer (), хотя здесь есть некоторые ошибки. См stackoverflow.com/questions/1646193/... и krishnabhargav.blogspot.dk/2009/06/...
RenniePet

4
Это фактически вносит ошибку в код Skeet; Если вы вызываете stream.Seek(1L, SeekOrigin.Begin), прежде чем вызывать с готовностью, если поток является потоком памяти, вы получите на 1 байт больше, чем если бы это был любой другой поток. Если вызывающая сторона ожидает чтения с того места, где находится текущая позиция, до конца потока, то вы не должны использовать CopyToили ToArray(); В большинстве случаев это не будет проблемой, но если звонящий не знает об этом странном поведении, он будет сбит с толку.
Лит

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

9
MemoryStream следует создавать с помощью «new MemoryStream (file.PostedFile.ContentLength)», чтобы избежать фрагментации памяти.
Дэн Рэндольф

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.
Тим

16

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

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

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

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+, созданное как метод расширения без ненужного вызова Dispose для MemoryStream. Это безнадежно тривиальная оптимизация, но стоит отметить, что сбой при утилизации MemoryStream не является реальной ошибкой.

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

Я не вижу, где этот код избегает повреждения данных. Вы можете это объяснить?
Ниппи

Допустим, у вас есть изображение, и вы хотите отправить его по SMTP. Вы, вероятно, будете использовать кодировку base64. По какой-то причине файл повреждается, если вы разбиваете его на байты. Однако использование двоичного считывателя позволит успешно отправить файл.
NothinRandom

3
Несколько стар, но я чувствовал, что стоит упомянуть - реализация @NothinRandom обеспечивает работу со строками, а не с потоками. Хотя в этом случае, вероятно, было бы проще всего использовать File.ReadAllBytes.
XwipeoutX

1
Downvote из-за опасного стиля кода (без автоматического удаления / использования).
Арни

К сожалению, допускается только -1, ничего общего с вопросом, параметром имени файла с именем input, без удаления, без буфера чтения, без filemode и двоичного считывателя для чтения побайтно, почему?
Аридане Аламо

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» не должен быть нулевым, а «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

При добавлении кода также кратко опишите предложенное решение.
якобом

-5

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

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

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


2
Что делать, если localStreamне MemoryStream? Этот код не удастся.
johnnyRose

localStream должен быть объектом на основе потока. подробнее об объекте на основе потока здесь stackoverflow.com/questions/8156896/…
Абба

1
То , что я пытался предложить это, если вы пытаетесь гипс localStreamк MemoryStream, но localStreamэто неMemoryStream , он будет не в состоянии . Этот код прекрасно скомпилируется, но может не работать во время выполнения, в зависимости от фактического типа localStream. Вы не всегда можете произвольно привести базовый тип к дочернему типу; читайте больше здесь . Это еще один хороший пример, который объясняет, почему вы не всегда можете сделать это.
johnnyRose

Чтобы уточнить мой комментарий выше: все MemoryStreams являются Streams, но не все Streams являются MemoryStreams.
johnnyRose

все основанные на Stream объекты имеют Stream в качестве базового типа. И сам поток всегда может быть преобразован в поток памяти. Независимо от того, какой потоковый объект вы пытаетесь преобразовать в Meomry Stream, он всегда должен работать. Наша цель здесь - преобразовать объект потока в массив байтов. Можете ли вы дать мне случай, когда он потерпит неудачу?
Абба
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.