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

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

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

Stream s;
byte[] b;

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

По-прежнему ли лучше читать и записывать фрагменты потока?

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

Marc Gravell 21.10.2008 17:57

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

0xced 17.05.2019 11:19

Разве Binary Reader не прикрепляет к потоку кодировку UTF-8? Разве это не проблема, если вы не читаете текст (например, читаете изображение и т. д.)? docs.microsoft.com/en-us/dotnet/api/…

JMarsch 08.01.2021 05:15
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
958
3
949 242
16
Перейти к ответу Данный вопрос помечен как решенный

Ответы 16

Ответ принят как подходящий

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

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

@Jon, стоит упомянуть yoda.arachsys.com/csharp/readbinary.html

Sam Saffron 13.02.2009 02:11

@Jon Кажется, я должен установить input.Position на 0 (input.Position = 0), чтобы чтение работало. В противном случае цикл while игнорируется. Обычно мне нужно «копировать / сохранять» входной поток и использовать его в дальнейшем?

Jeff 20.03.2012 04:41

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

Jon Skeet 20.03.2012 10:42

@Jon, извини, если это звучит глупо, но разве "перемотка" не должна быть частью ReadFully? Или это ответственность вызывающего этого метода?

Jeff 21.03.2012 14:17

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

Jon Skeet 21.03.2012 14:19

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

Anyname Donotcare 12.04.2012 14:30

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

caesay 16.04.2012 06:18

@JonSkeet есть ли какая-то особая причина для 16*1024?

Royi Namir 13.11.2012 11:48

@RoyiNamir: Во многих случаях это просто разумное значение - балансировка пропускной способности с памятью.

Jon Skeet 13.11.2012 12:06

@just_name В монопроекте также используется 10*1024 в своей реализации.

antonijn 22.03.2013 20:27

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

Jon Skeet 24.01.2019 17:15

По моему опыту, Stream.Copy Чтобы не выполнить мой файл размером 50 МБ, он выдает исключение «Недостаточно памяти». Но решение @JonSkeet работает. Значит, они не такие

chinh nguyen van 11.06.2020 11:36

Просто хочу отметить, что если у вас есть 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. Конечно, пример также явно неполон в том, как он использует неинициализированную переменную.

Roman Starkov 31.07.2010 03:48

Правильно, спасибо, что указали на это. Тем не менее, точка по-прежнему означает MemoryStream, поэтому я исправил ее, чтобы отразить это.

Fernando Neira 06.10.2010 14:21

Просто отметьте, что для MemoryStream другой возможностью является MemoryStream.GetBuffer (), хотя здесь есть некоторые подводные камни. См. stackoverflow.com/questions/1646193/… и krishnabhargav.blogspot.dk/2009/06/….

RenniePet 02.01.2013 09:29

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

leat 07.08.2015 11:26
MemoryStream ms = new MemoryStream();
file.PostedFile.InputStream.CopyTo(ms);
var byts = ms.ToArray();
ms.Dispose();

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

Dan Randolph 20.06.2016 23:04

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

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

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

Чем отличается ваш ответ от ответа Джона? Также я должен сделать это input.Position = 0 для работы CopyTo.

Jeff 20.03.2012 04:42

@nathan, прочитайте файл из веб-клиента (filizesize = 1mb) - iis должен будет загрузить весь 1mb в свою память, верно?

Royi Namir 13.11.2012 12:14

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

Nathan Phillips 26.02.2013 20:20

@RoyiNamir, вы правы, цель этого кода - прочитать все содержимое потока в память. Я не совсем уверен, что вы спрашиваете, но если вы используете WebClient и этот код на стороне клиента с IIS в качестве сервера, то IIS не будет читать весь файл в память, но клиент будет.

Nathan Phillips 26.02.2013 20:24

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

Jodrell 27.03.2013 16:19

@ Джодрелл согласен, как и ответ Фернандо-Нейры stackoverflow.com/a/2630539/1037948

drzaus 21.09.2015 23:30

@Jodrell, это будет только для оптимизации скорости. Решения об оптимизации скорости всегда следует принимать в контексте ожидаемого использования. Если выполнение проверки для MemoryStream замедляет код на 1/1000, то это следует делать только в том случае, если MemoryStream, вероятно, будет передан хотя бы один раз из каждой тысячи раз, в противном случае у вас будет совокупное замедление.

Nathan Phillips 22.09.2015 13:06

Для меня @NathanPhillips больше похоже на одновременное уравнение. Не следует ли при принятии такого решения учитывать затраты на копирование MemoryStream на MemoryStream?

Jodrell 22.09.2015 14:10

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

Nathan Phillips 22.09.2015 17:47

Нет причин для удаления MemoryStream; и этот ответ не делает ничего, кроме неправильной критики другого ответа.

SensorSmith 18.10.2018 01:50

Следует отметить, что пример @NathanPhillips скопирует входной Stream из его текущей позиции. Это может заманить в ловушку тех, кто полагает, что CopyTo() скопирует весь поток; возможно потребуется установить stream.Position = 0;. Stream.CopyTo ()

Aaron 11.01.2019 03:56

НИКОГДА не создавайте новый экземпляр MemoryStream без оценки максимального размера. Если вы этого не сделаете, вы можете изнутри без надобности копировать множество меньших массивов.

Imre Pühvel 01.04.2020 11:40

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

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 - плохая идея. Эта ответственность должна лежать на процедуре вызова.

Jeff 22.08.2013 23:33

То, что указано выше, в порядке ... но вы столкнетесь с повреждением данных при отправке материалов по 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 11.10.2012 11:38

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

NothinRandom 30.10.2012 03:36

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

XwipeoutX 11.04.2014 10:35

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

arni 01.12.2017 20:10

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

Aridane Álamo 07.05.2020 21:54

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

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 не был доступен на Stream до 4.0.

Tim 14.10.2014 22:31

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

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

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

Что делать, если localStream не является MemoryStream? Этот код не сработает.

johnnyRose 17.03.2017 18:35

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

Abba 17.03.2017 23:48

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

johnnyRose 18.03.2017 00:01

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

johnnyRose 18.03.2017 00:05

все объекты на основе Stream имеют Stream в качестве базового типа. А сам Stream всегда можно преобразовать в поток памяти. Независимо от того, какой объект на основе потока вы пытаетесь передать в Meomry Stream, он всегда должен работать. Наша цель здесь - преобразовать объект потока в массив байтов. Можете ли вы привести мне хоть один случай, когда он потерпит неудачу?

Abba 20.03.2017 20:38

Это просто неправильно. Простой пример: 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, как указано выше, создает исключение времени выполнения, как я объяснил ранее. Попробуйте и убедитесь сами.

johnnyRose 20.03.2017 21:42

Если у вас есть еще вопросы, нам, вероятно, следует переместить этот разговор в чат. В противном случае попробуйте мой код, и вы увидите. Примеры здесь с использованием CopyTo верны - код в вашем ответе будет работать, только если (localStream is MemoryStream) == true, как указано ранее.

johnnyRose 20.03.2017 21:47

Теперь я понимаю вашу точку зрения, CopyTo - это то, что вам нужно. Спасибо за разъяснения

Abba 21.03.2017 00:26
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 17.05.2017 16:51

Когда вы добавляете код, также вкратце опишите ваше предлагаемое решение.

yakobom 17.05.2017 17:09

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

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

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

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

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

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

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

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

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

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

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

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