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


676

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

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

Stream s; 
byte[] b; 

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

Неужели по-прежнему лучше читать и писать куски потока?

+49

Конечно, другой вопрос: * должен * создать байт [] из потока ... для больших данных предпочтительнее обрабатывать поток, как, ну, поток! 21 окт. 082008-10-21 13:57:27

992

Это действительно зависит от того, можете ли вы доверять 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 для получения дополнительной информации (и альтернативной реализации).

+1

@Jon: если вы знаете точную длину своего потока, можно ли использовать код, который написал постер? 10 ноя. 082008-11-10 12:43:06

+1

@Pure: с помощью BinaryReader да - цикл будет циклическим до тех пор, пока поток не станет пустым или данные будут прочитаны. Однако не пытайтесь использовать Stream. 10 ноя. 082008-11-10 12:44:25

+8

@ Jon, может быть, стоит упомянуть http://www.yoda.arachsys.com/csharp/readbinary.html 12 фев. 092009-02-12 23:11:33

  0

@Jon Кажется, я должен установить input.Position на 0 (input.Position = 0) для чтения работать. В противном случае цикл while будет проигнорирован. Как правило, мне нужно «копировать/сохранять» входной поток и использовать его позже? 20 мар. 122012-03-20 00:41:58

+4

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

  0

@ Жаль, если это звучит глупо, но не следует ли «перематывать» часть ReadFully? Или это ответственность вызывающего этого метода? 21 мар. 122012-03-21 10:17:41

+3

@Jeff: Это ответственность вызывающего. В конце концов, поток не может быть доступен для поиска (например, сетевой поток) или просто нет необходимости перематывать его. 21 мар. 122012-03-21 10:19:27

+12

Могу ли я спросить, почему именно «16 * 1024»? 12 апр. 122012-04-12 10:30:50

+4

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

  0

@achekh: Нет, абсолютно нет. На каждой итерации вы можете прочитать начало массива - потому что единственный раз, когда используются данные, - это вызов 'Write' в той же итерации. 03 сен. 122012-09-03 14:54:26

+1

@JonSkeet есть ли какая-то особая причина для '16 * 1024'? 13 ноя. 122012-11-13 07:48:53

+1

@RoyiNamir: Во многих случаях это разумная ценность, балансируя пропускную способность с памятью. 13 ноя. 122012-11-13 08:06:47

  0

Он не работает с файлом, связанным с памятью :( 05 мар. 132013-03-05 17:06:09

+2

@MohamedSakherSawan: В этом комментарии вам не хватает контекста. Я предлагаю вам задать новый вопрос с более подробной информацией. 05 мар. 132013-03-05 17:07:12

  0

Thanx Jon, я уже спросил два вопроса: http://stackoverflow.com/questions/15229166/read-memory-mapped-file-or-knowing-its-size-to-read-it-correctely и http: // stackoverflow.com/questions/14953393/read-all-contents-of-memory-mapped-file-or-memory-mapped-view-accessor-no-k 05 мар. 132013-03-05 17:10:41

+1

@just_name В монопроекте также используется '10 * 1024' в своих реализация. 22 мар. 132013-03-22 16:27:44

  0

У меня есть несколько вопросов к этому решению: будет ли он работать и для файлов с несколькими (сотнями) мегабайт? И будет ли он работать и для мультимедийных файлов (например, изображений или музыки) с размером буфера 16KiB? 23 июл. 132013-07-23 09:32:11

+1

@Storm: Ну, он читается в памяти, так что это зависит от того, сколько у вас памяти. Но с достаточной памятью он работал бы на несколько сотен мегабайтных файлов. И да, буфер 16K отлично подходит для мультимедийных файлов - он не обращает внимания на данные, которые он читает вообще. Это всего лишь двоичный поток. 23 июл. 132013-07-23 16:52:12

  0

@ JonSkeet В своем ответе вы говорите: «« Stream.Read »не гарантирует, что он прочитает все, о чем просят. Если вы читаете из сетевого потока, например, он может читать стоимость одного пакета, а затем вернуться, даже если скоро будет больше данных ». Я не уверен, что я следую, потому что вы используете этот метод в своем решении? Или я что-то пропустил? 23 ноя. 162016-11-23 22:45:04

+1

@silkfire: использование метода в порядке - но вам нужно использовать возвращаемое значение, чтобы узнать, сколько данных вы прочитали, и даже если вы читаете меньшую, чем запрошенную сумму, помните, что все еще может быть больше данных для чтения. 24 ноя. 162016-11-24 06:42:37

  0

Все, а не '10 * 1024', что составляет 10 МБ и слишком велико для текстового блока, если вы спросите меня, я уменьшил его до 512 (КБ), и он отлично работает для меня в текстовом файле 1,5 МБ , 12 апр. 172017-04-12 21:19:46

  0

@vapcguy: Um, 10 * 1024 составляет 10 * килобайт *, а не 10 * мегабайт *. Поэтому, если вы установите его на 512 КБ, вы значительно увеличили его ... 13 апр. 172017-04-13 06:23:35

  0

@JonSkeet Спасибо за разъяснение - на самом деле я установил его как раз «512» и просто подумал, что это был KB, по какой-то причине. Таким образом, на самом деле это всего лишь 1/2 (1024/10) по сравнению с показанным здесь или 5% размером с кусок. Теперь я могу увеличить его! :) 13 апр. 172017-04-13 17:11:15


77

Просто хочу указать, что если у вас есть 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); 
    } 
} 
  0

Да, для чего все это стоит? Даже с самыми щедрыми предположениями это работает только для потоков, которые уже являются «MemoryStream's». Конечно, пример также явно неполный, в том, как он использует неинициализированную переменную. 30 июл. 102010-07-30 23:48:41

+3

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

  0

Просто упомяните, что для MemoryStream еще одна возможность - MemoryStream.GetBuffer(), хотя есть некоторые проблемы. См. Http://stackoverflow.com/questions/1646193/why-does-memorystream-getbuffer-always-throw и http://krishnabhargav.blogspot.dk/2009/06/net-funda-memorystream-toarray-vs.html. 02 янв. 132013-01-02 05:29:01

+2

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


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

MemoryStream должен быть создан с помощью «нового MemoryStream (file.PostedFile.ContentLength)», чтобы избежать фрагментации памяти. 20 июн. 162016-06-20 20:04:36


8

Я получаю ошибку времени компиляции с кодом Боба (то есть вопросника). Stream.Length длинна, тогда как BinaryReader.ReadBytes принимает целочисленный параметр. В моем случае, я не ожидаю, чтобы иметь дело с Streams достаточно большим, чтобы требовать долгой точности, поэтому я использую следующее:

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

551

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

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

Чем отличается между вашим ответом и Джоном? Также я должен сделать это input.Position = 0 для работы CopyTo. 20 мар. 122012-03-20 00:42:50

  0

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

+3

@Jeff, мой ответ будет работать только на .Net 4 или выше, Jons будет работать на более низких версиях, переписывая функциональность, предоставленную нам в более поздней версии. Вы правы, что CopyTo будет копировать только с текущей позиции, если у вас есть поток Seekable и вы хотите скопировать с самого начала, то вы можете перейти к началу с помощью своего кода или ввода. Seee (0, SeekOrigin.Begin), хотя во многих случаях ваш поток может быть не доступен. 26 фев. 132013-02-26 16:20:46

  0

@RoyiNamir, вы правы, что целью этого кода является считывание всего содержимого потока в память. Я точно не знаю, что вы просите, но если вы используете WebClient и этот код на стороне клиента с IIS в качестве сервера, то IIS не будет считывать весь файл в память, но клиент будет. 26 фев. 132013-02-26 16:24:14

+5

, возможно, стоит проверить, является ли 'input' уже' MemorySteam' и короткое замыкание. Я знаю, что было бы глупо от вызывающего, чтобы передать «MemoryStream», но ... 27 мар. 132013-03-27 12:19:59

  0

@ Джодрелл согласен, как и ответ Фернандо-Нейры http://stackoverflow.com/a/2630539/1037948 21 сен. 152015-09-21 20:30:14

  0

@ Джодрелл, делая это только для оптимизации скорости.Решения о оптимизации скорости всегда должны приниматься в контексте ожидаемого использования. Если проверка «MemoryStream» делает код 1/1000 медленнее, это должно быть сделано только в том случае, если «MemoryStream», скорее всего, будет передаваться, по крайней мере, раз в тысячу раз, иначе у вас будет агрегатное замедление. 22 сен. 152015-09-22 10:06:43

  0

@NathanPhillips звучит скорее как одновременное уравнение для меня. Разве затраты на копирование «MemoryStream» в «MemoryStream» не учитываются в этом решении? 22 сен. 152015-09-22 11:10:14

+3

@Jodrell, Точно так. Если вы копируете миллионы небольших потоков в память, а один из них - «MemoryStream», то есть ли оптимизация имеет смысл в вашем контексте - это сравнение времени, затраченного на миллионы преобразований типов, против времени, затраченного на копирование это «MemoryStream» в другой «MemoryStream». 22 сен. 152015-09-22 14:47:01


8

Вы можете даже сделать его любителю с расширениями:

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

Я думаю, что это плохая идея поместить поток ввода в блок использования. Эта ответственность должна соответствовать процедуре вызова. 22 авг. 132013-08-22 19:33:05


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

Я не вижу, где этот код избегает повреждения данных. Вы можете это объяснить? 11 окт. 122012-10-11 07:38:27

  0

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

+1

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

  0

Downvote из-за опасного стиля кода (без автоматического удаления/использования). 01 дек. 172017-12-01 17:10:31


41

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

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

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

+3

Обратите внимание, что это не будет работать в .NET 3.5 и ниже, так как «CopyTo» недоступен в 'Stream' до 4.0. 14 окт. 142014-10-14 18:31:13


-2

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

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

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

+2

Что делать, если 'localStream' не является' MemoryStream'? Этот код не сработает. 17 мар. 172017-03-17 15:35:49

  0

localStream должен быть объектом, основанным на потоке. подробнее об объекте, основанном на потоках, здесь http://stackoverflow.com/questions/8156896/difference-between-memory-stream-and-filestream 17 мар. 172017-03-17 20:48:04

+1

То, что я пытаюсь предложить, заключается в том, что если вы попытаетесь использовать 'localStream' в' MemoryStream ', но' localStream' ** не ** 'MemoryStream', он _will_ терпит неудачу. Этот код будет компилироваться отлично, но он может выйти из строя во время выполнения, в зависимости от фактического типа 'localStream'. Вы не всегда можете произвольно использовать базовый тип для дочернего типа; [читайте больше здесь] (http://stackoverflow.com/a/4453259/2840103). [Это еще один хороший пример] (http://stackoverflow.com/a/729532/2840103), который объясняет, почему вы не всегда можете это сделать. 17 мар. 172017-03-17 21:01:19

  0

Чтобы продумать мой комментарий выше: все потоки памяти - потоки, но не все потоки - это потоки памяти. 17 мар. 172017-03-17 21:05:55

  0

все объекты, основанные на потоке, имеют Stream как базовый тип. И сам поток всегда может быть конвертирован в поток памяти. Независимо от того, какой объект, основанный на потоках, вы пытаетесь передать в Meomry Stream, он всегда должен работать. Наша цель здесь - преобразовать объект stream в массив байтов. Можете ли вы рассказать мне, где это произойдет? 20 мар. 172017-03-20 17:38:12

  0

Это просто неправильно. Простой пример: 'FileStream' не может быть отправлен в« MemoryStream »и с этой ошибкой завершится ошибкой:« Невозможно наложить объект типа «System.IO.FileStream» на тип «System.IO.MemoryStream». » Пример: 'using (поток fs = новый FileStream (@" C: \ pathtofile.txt ", FileMode.Open)) { var memoryStream = (MemoryStream) fs; } ' Это не будет компилироваться, если вы просто используете' var', потому что он будет неявно вводить 'MemoryStream'. Ввод его с помощью «Stream», как указано выше, создает исключение во время выполнения, как я объяснял ранее. Попробуйте и убедитесь сами. 20 мар. 172017-03-20 18:42:44

  0

Если у вас есть еще вопросы, мы, вероятно, должны перевести этот разговор в чат. В противном случае попробуйте мой код, и вы увидите. Примеры здесь, используя «CopyTo», верны - код в вашем ответе будет работать, только если '(localStream - MemoryStream) == true', как указано ранее. 20 мар. 172017-03-20 18:47:24

  0

Теперь, я получаю вашу точку, 'CopyTo' - это путь. Спасибо за разъяснения 20 мар. 172017-03-20 21:26:03


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

Вы просто скопировали код из ответа №1 и №3, не добавив ничего ценного. Пожалуйста, не делай этого. :) 17 май. 172017-05-17 13:51:59

  0

Когда вы добавляете код, также кратко опишите предлагаемое решение. 17 май. 172017-05-17 14:09:13


0

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

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