Каков предпочтительный метод создания массива байтов из входного потока?
Вот мое текущее решение с .NET 3.5.
Stream s;
byte[] b;
using (BinaryReader br = new BinaryReader(s))
{
b = br.ReadBytes((int)s.Length);
}
По-прежнему ли лучше читать и записывать фрагменты потока?
На самом деле вам, вероятно, следует использовать поток вместо байта []. Но есть некоторые системные API, которые не поддерживают потоки. Например, вы не можете создать X509Certificate2 из потока, вы должны дать ему byte [] (или строку). В этом случае это нормально, поскольку сертификат x509, вероятно, не большие данные.
Разве Binary Reader не прикрепляет к потоку кодировку UTF-8? Разве это не проблема, если вы не читаете текст (например, читаете изображение и т. д.)? docs.microsoft.com/en-us/dotnet/api/…





Это действительно зависит от того, доверяете ли вы 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
@Jon Кажется, я должен установить input.Position на 0 (input.Position = 0), чтобы чтение работало. В противном случае цикл while игнорируется. Обычно мне нужно «копировать / сохранять» входной поток и использовать его в дальнейшем?
@Jeff: У нас действительно нет контекста, но если вы писали в поток, то да, вам нужно «перемотать» его перед чтением. Есть только один «курсор», указывающий, где вы находитесь в потоке, - не один для чтения, а отдельный для записи.
@Jon, извини, если это звучит глупо, но разве "перемотка" не должна быть частью ReadFully? Или это ответственность вызывающего этого метода?
@ Джефф: Это ответственность вызывающего абонента. В конце концов, поток может быть недоступен для поиска (например, сетевой поток) или может просто не быть необходимости перематывать его.
Могу я спросить, почему именно 16*1024?
@just_name: я не знаю, имеет ли это какое-то значение, но (16 * 1024) оказывается половиной Int16.MaxValue :)
@JonSkeet есть ли какая-то особая причина для 16*1024?
@RoyiNamir: Во многих случаях это просто разумное значение - балансировка пропускной способности с памятью.
@just_name В монопроекте также используется 10*1024 в своей реализации.
@Quantum: Пожалуйста, не редактируйте сообщения таким семантически значимым образом. Предлагаемое вами изменение нарушило бы код: GetBuffer может возвращать буфер, размер которого превышает фактически используемые данные. Этот метод возвращает массив, который точно соответствует размеру прочитанных данных.
По моему опыту, Stream.Copy Чтобы не выполнить мой файл размером 50 МБ, он выдает исключение «Недостаточно памяти». Но решение @JonSkeet работает. Значит, они не такие
Просто хочу отметить, что если у вас есть 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. Конечно, пример также явно неполон в том, как он использует неинициализированную переменную.
Правильно, спасибо, что указали на это. Тем не менее, точка по-прежнему означает MemoryStream, поэтому я исправил ее, чтобы отразить это.
Просто отметьте, что для MemoryStream другой возможностью является MemoryStream.GetBuffer (), хотя здесь есть некоторые подводные камни. См. stackoverflow.com/questions/1646193/… и krishnabhargav.blogspot.dk/2009/06/….
Это фактически вносит ошибку в код Skeet; Если вы вызываете stream.Seek(1L, SeekOrigin.Begin), перед тем, как вы вызываете его, если поток является потоком памяти, вы получите на 1 байт больше, чем если бы это был любой другой поток. Если вызывающий абонент ожидает чтения с текущей позиции до конца потока, вы не должны использовать CopyTo или ToArray(); В большинстве случаев это не будет проблемой, но если вызывающий абонент не знает об этом необычном поведении, он будет сбит с толку.
MemoryStream ms = new MemoryStream();
file.PostedFile.InputStream.CopyTo(ms);
var byts = ms.ToArray();
ms.Dispose();
MemoryStream следует создавать с помощью «new MemoryStream (file.PostedFile.ContentLength)», чтобы избежать фрагментации памяти.
Я получаю ошибку времени компиляции с кодом Боба (то есть спрашивающего). 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.
@nathan, прочитайте файл из веб-клиента (filizesize = 1mb) - iis должен будет загрузить весь 1mb в свою память, верно?
@Jeff, мой ответ будет работать только на .Net 4 или выше, Джонс будет работать с более ранними версиями, переписав функциональность, предоставленную нам в более поздней версии. Вы правы, CopyTo будет копировать только из текущей позиции, если у вас есть поток Seekable, и вы хотите скопировать с самого начала, вы можете перейти к началу, используя свой код или input.Seek (0, SeekOrigin.Begin), хотя во многих случаях ваш поток может быть недоступен для поиска.
@RoyiNamir, вы правы, цель этого кода - прочитать все содержимое потока в память. Я не совсем уверен, что вы спрашиваете, но если вы используете WebClient и этот код на стороне клиента с IIS в качестве сервера, то IIS не будет читать весь файл в память, но клиент будет.
Возможно, стоит проверить, не является ли input уже MemorySteam, и произошло короткое замыкание. Я знаю, что со стороны вызывающего абонента было бы глупо передавать MemoryStream, но ...
@ Джодрелл согласен, как и ответ Фернандо-Нейры stackoverflow.com/a/2630539/1037948
@Jodrell, это будет только для оптимизации скорости. Решения об оптимизации скорости всегда следует принимать в контексте ожидаемого использования. Если выполнение проверки для MemoryStream замедляет код на 1/1000, то это следует делать только в том случае, если MemoryStream, вероятно, будет передан хотя бы один раз из каждой тысячи раз, в противном случае у вас будет совокупное замедление.
Для меня @NathanPhillips больше похоже на одновременное уравнение. Не следует ли при принятии такого решения учитывать затраты на копирование MemoryStream на MemoryStream?
@Jodrell, именно так. Если вы копируете миллионы небольших потоков в память, и одним из них является MemoryStream, то целесообразность оптимизации в вашем контексте - это сравнение времени, затраченного на выполнение миллионов преобразований типов, со временем, затраченным на копирование того, который MemoryStream в другой MemoryStream.
Нет причин для удаления MemoryStream; и этот ответ не делает ничего, кроме неправильной критики другого ответа.
Следует отметить, что пример @NathanPhillips скопирует входной Stream из его текущей позиции. Это может заманить в ловушку тех, кто полагает, что CopyTo() скопирует весь поток; возможно потребуется установить stream.Position = 0;. Stream.CopyTo ()
НИКОГДА не создавайте новый экземпляр MemoryStream без оценки максимального размера. Если вы этого не сделаете, вы можете изнутри без надобности копировать множество меньших массивов.
Вы даже можете сделать его более привлекательным с помощью расширений:
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 - плохая идея. Эта ответственность должна лежать на процедуре вызова.
То, что указано выше, в порядке ... но вы столкнетесь с повреждением данных при отправке материалов по 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 обеспечивает работу со строками, а не с потоками. Однако в этом случае, вероятно, было бы проще использовать File.ReadAllBytes.
Голосование против опасного стиля кода (без автоматического удаления / использования).
К сожалению, разрешено только -1, ничего общего с вопросом, параметр имени файла с именем input, not dising, без буфера чтения, без файлового режима и двоичный читатель для чтения побайтно, почему?
только моя пара центов ... практика, которую я часто использую, состоит в том, чтобы организовать такие методы как пользовательский помощник
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.
я смог заставить его работать в одной строке:
byte [] byteArr= ((MemoryStream)localStream).ToArray();
как поясняется Джонни Роуз, приведенный выше код будет работать только для MemoryStream
Что делать, если localStream не является MemoryStream? Этот код не сработает.
localStream должен быть объектом на основе потока. подробнее об объекте на основе потока здесь stackoverflow.com/questions/8156896/…
Я пытался предложить следующее: если вы попытаетесь преобразовать localStream в MemoryStream, но localStream будет нет или MemoryStream, воля не удастся. Этот код будет компилироваться нормально, но может дать сбой во время выполнения, в зависимости от фактического типа localStream. Вы не всегда можете произвольно привести базовый тип к дочернему типу; подробнее здесь. Это еще один хороший пример, который объясняет Почему, вы не всегда можете это сделать.
Чтобы уточнить мой вышеупомянутый комментарий: все MemoryStreams являются Streams, но не все Streams являются MemoryStreams.
все объекты на основе Stream имеют Stream в качестве базового типа. А сам Stream всегда можно преобразовать в поток памяти. Независимо от того, какой объект на основе потока вы пытаетесь передать в Meomry Stream, он всегда должен работать. Наша цель здесь - преобразовать объект потока в массив байтов. Можете ли вы привести мне хоть один случай, когда он потерпит неудачу?
Это просто неправильно. Простой пример: 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, как указано выше, создает исключение времени выполнения, как я объяснил ранее. Попробуйте и убедитесь сами.
Если у вас есть еще вопросы, нам, вероятно, следует переместить этот разговор в чат. В противном случае попробуйте мой код, и вы увидите. Примеры здесь с использованием CopyTo верны - код в вашем ответе будет работать, только если (localStream is MemoryStream) == true, как указано ранее.
Теперь я понимаю вашу точку зрения, CopyTo - это то, что вам нужно. Спасибо за разъяснения
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, не добавив ничего ценного. Пожалуйста, не делай этого. :)
Когда вы добавляете код, также вкратце опишите ваше предлагаемое решение.
Вы можете использовать этот метод расширения.
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();
Конечно, другой вопрос: должен вы создаете byte [] из потока ... для больших данных предпочтительно рассматривать поток как поток!