Створення масиву байтів із потоку


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

Відповіді:


1293

Це дійсно залежить від того, можна довіряти чи ні 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буде продовжуватись до кінця потоку чи вказаного Вами розміру, але Ви все ще повинні знати розмір, з якого слід почати.

Вищеописаний метод буде постійно читати (і копіювати в a 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 :)
ceesay

734

Хоча відповідь Джона правильна, він переписує код, який вже існує в CopyTo. Тож для .Net 4 використовуйте рішення Сандіпа, але для попередньої версії .Net використовуйте відповідь Йона. Код Сандіпа буде вдосконалено шляхом використання "використання", оскільки винятки CopyToв багатьох ситуаціях є досить ймовірними і залишать MemoryStreamне видалені.

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

6
Що відрізняється між вашою відповіддю та Джоном? Також я повинен зробити це введення.Position = 0, щоб CopyTo працював.
Джефф

1
@nathan, readig файл з веб-клієнта (filizeize = 1mb) - iis повинен буде завантажити весь 1mb на свою пам'ять, чи не так?
Рой Намір

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

5
можливо, варто перевірити, чи inputвже є MemorySteamкоротке замикання. Я знаю, що було б нерозумно абонента передати, MemoryStreamале ...
Джодрелл

3
@Jodrell, саме так. Якщо ви копіюєте мільйони маленьких потоків в пам'ять і один з них є MemoryStreamте , чи має оптимізація сенс у вашому контексті є порівняння часу , що витрачається робити мільйони перетворення типів проти часу , що витрачається скопіювати той , який це MemoryStreamв інший MemoryStream.
Натан Філіпс

114

Просто хочу зазначити, що у випадку, якщо у вас є 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);
    }
}

1
Ага, на що всі гроші? Навіть з найбільш щедрими припущеннями, це працює лише для потоків, які вже є MemoryStream. Звичайно, приклад також очевидно неповний, як він використовує неініціалізовану змінну.
Роман Старков

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 слід створити за допомогою "нового 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.
Тім

15

Ви можете просто використовувати метод 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 дозволено, нічого спільного з питанням, параметр імені файлу з ім'ям вводу, не розпоряджається, ні буфер читання, ні файловий модуль, а двійковий читач для читання байтів у байтах, чому?
Арідан Аламо

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/…
Abba

1
Те, що я намагався запропонувати, це, якщо ви спробуєте перейти localStreamна "a" MemoryStream, але localStreamце не " a" MemoryStream, це не вдасться. Цей код буде складено штрафом, але він може вийти з ладу під час виконання, залежно від фактичного типу localStream. Ви не завжди можете довільно надати базовий тип дочірньому типу; докладніше читайте тут . Це ще один хороший приклад, який пояснює, чому ви не завжди можете це зробити.
johnnyRose

Щоб уточнити мій вище коментар: усі MemoryStreams - це потоки, але не всі потоки - це MemoryStreams.
johnnyRose

всі об'єкти на основі потоку мають базовий тип Stream. І сам потік завжди може бути конвертованим у потік пам'яті. Незалежно від того, який об’єкт, що базується на потоці, ви намагаєтеся передати на Meomry Stream, він повинен завжди працювати. Наша мета тут - перетворити об'єкт потоку в масив байтів. Чи можете ви дати мені випадок, коли це не вдасться?
Абба
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.