Как сохранить поток в файл на C#?

У меня есть объект StreamReader, который я инициализировал потоком, теперь я хочу сохранить этот поток на диск (поток может быть .gif, .jpg или .pdf).

Существующий код:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. Мне нужно сохранить это на диск (у меня есть имя файла).
  2. В будущем я, возможно, захочу сохранить это на SQL Server.

У меня также есть тип кодировки, который мне понадобится, если я сохраню его на SQL Server, правильно?

Что такое myOtherObject?

anhtv13 08.08.2019 10:09
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
753
1
831 318
10

Ответы 10

Вы не должен используете StreamReader для двоичных файлов (например, gif или jpgs). StreamReader предназначен для данных текст. Вы почти потеряете безусловно, если будете использовать его для произвольных двоичных данных. (Если вы используете Encoding.GetEncoding (28591), вероятно, все будет в порядке, но какой в ​​этом смысл?)

Зачем вообще нужно использовать StreamReader? Почему бы просто не сохранить двоичные данные в виде и записать их обратно на диск (или в SQL) как двоичные данные?

Обновлено: Поскольку это похоже на то, что люди хотят видеть ... если вы делать просто хотите скопировать один поток в другой (например, в файл), используйте что-то вроде этого:

/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[8 * 1024];
    int len;
    while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, len);
    }    
}

Чтобы использовать его для сброса потока в файл, например:

using (Stream file = File.Create(filename))
{
    CopyStream(input, file);
}

Обратите внимание, что Stream.CopyTo был введен в .NET 4 и служит в основном той же цели.

Это похоже на такой частый случай, я удивлен, что его нет в .NET. Я вижу, как люди создают байтовые массивы размером с весь файл, что может вызвать проблемы с большими файлами.

Tilendor 07.12.2010 20:00

@Tilendor: он присутствует как метод расширения в .NET 4. (CopyTo)

Jon Skeet 07.12.2010 20:17

Я не думаю, что это метод расширения, но он новый в классе Stream.

Kugel 25.01.2011 00:43

@Kugel: Вы правы, извините. я имел его как метод расширения в служебной библиотеке, но теперь, когда он находится в самом Stream, мой метод расширения не вызывается.

Jon Skeet 25.01.2011 01:02

Почему вы устанавливаете размер буфера 8192? Спасибо

Florian 02.10.2013 20:46

@ Флориан: Это достаточно произвольно - достаточно маленькое значение, чтобы не занимать слишком много памяти, и достаточно большое, чтобы передавать разумный кусок за раз. Было бы хорошо быть 16 КБ, может быть, 32 КБ - я просто был бы осторожен, чтобы не попасть в кучу больших объектов.

Jon Skeet 02.10.2013 20:53

@JonSkeet: как это можно использовать, когда вводом является HttpWebResponse (файл, возвращаемый из приложения веб-API)?

B. Clay Shannon 20.02.2014 22:03

В WPF я не мог выполнить File.Create, но мог сделать оператор using, похожий на using (FileStream fs = new FileStream(destPath, FileMode.CreateNew, FileAccess.Write), и добавить в него CopyStream(stream, fs);.

vapcguy 12.10.2016 17:47

@vapcguy: вы сможете без проблем использовать File.Create из WPF.

Jon Skeet 12.10.2016 18:25

@JonSkeet Вы правы. Просто попробовал снова, сегодня, и теперь красная волнистая линия на Create исчезает, поэтому я не мог сказать вам, в чем была моя ошибка, но это не давало мне интеллекта, когда я затем вводил первую скобку после нее, прежде , ни показания мне при наведении курсора на File. Думаю, это была временная ошибка VS.

vapcguy 12.10.2016 23:36

@JonSkeet Нет ... это было. :) Теперь он работает, и ничего не делал, кроме открытия / закрытия / повторного открытия файла в VS, поэтому не могу вам сказать ...

vapcguy 12.10.2016 23:37

Отличный ответ. В большинстве других ответов используется CopyTo, доступный не во всех версиях .Net.

nivs1978 23.02.2017 00:33

@JonSkeet Я вижу, что размер буфера по умолчанию для Stream.CopyTo - это _DefaultCopyBufferSize = 81920;, поэтому, вероятно, можно с уверенностью предположить, что его можно использовать, как new byte[81920];, везде и даже в Java, например new byte[65536]; (также кратно 4096)?

Pierre 31.10.2019 09:11

@Pierre: Множество 4K, вероятно, не имеют большого значения, хотя это будет зависеть от потока. (И Java не имеет ограничения в 64 КБ для байтовых массивов ... Я не уверен, почему вы предлагаете здесь разницу.) Но, как всегда, вам нужно сбалансировать выделение памяти с меньшим количеством копий. Учитывая, что это не долгоживущий буфер, это, вероятно, не имеет большого значения, но может зависеть от вашего приложения.

Jon Skeet 31.10.2019 09:42

Этот код у меня не сработал. Stream.CopyTo - сработало.

Yaron 26.02.2021 16:11

@Yaron: Есть еще какая-то информация, кроме «у меня не работает»? В каком способ не заработало?

Jon Skeet 26.02.2021 16:59

@JonSkeet Я загрузил файл (документ Word) из Microsoft OneDrive через Graph API. Когда я сохранил файл локально с помощью описанного выше метода CopyStream (..) и попытался открыть его, я получил сообщение об ошибке «Word обнаружил нечитаемое содержимое». При переходе на Stream.CopyTo (..) - файл открылся правильно.

Yaron 28.02.2021 12:01

@Yaron: Есть много диагностической информации, которую я хотел бы спросить об этом ... Я предлагаю вам задать новый вопрос с минимальный воспроизводимый пример.

Jon Skeet 28.02.2021 12:37

@JonSkeet Stream.CopyTo тоже не работал. у меня работает byte [] ReadAsBytes (Stream input) {using (var ms = new MemoryStream ()) {input.CopyTo (ms); return ms.ToArray (); }} Я правда еще не нашел времени, чтобы это проанализировать.

Yaron 10.03.2021 18:30

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

Jon Skeet 10.03.2021 18:32

Почему бы не использовать объект FileStream?

public void SaveStreamToFile(string fileFullPath, Stream stream)
{
    if (stream.Length == 0) return;

    // Create a FileStream object to write a stream to a file
    using (FileStream fileStream = System.IO.File.Create(fileFullPath, (int)stream.Length))
    {
        // Fill the bytes[] array with the stream data
        byte[] bytesInStream = new byte[stream.Length];
        stream.Read(bytesInStream, 0, (int)bytesInStream.Length);

        // Use FileStream object to write to the specified file
        fileStream.Write(bytesInStream, 0, bytesInStream.Length);
     }
}

что, если входной поток имеет длину 1 ГБ - этот код попытается выделить буфер размером 1 ГБ :)

Buthrakaur 30.06.2011 11:56

Это не работает с потоком ответа, потому что его длина неизвестна.

Tomas Kubes 18.10.2013 14:31

Хотя это правда, что у вас должна быть память для byte[], я думаю, что будет редко, когда вы будете транслировать большой двоичный объект размером 1 ГБ + в файл ... если у вас нет сайта, который хранит торренты DVD ... К тому же, в наши дни на большинстве компьютеров доступно как минимум 2 ГБ ОЗУ ... Предостережение справедливо, но я думаю, что это тот случай, когда он, вероятно, «достаточно хорош» для большинства рабочих мест.

vapcguy 12.10.2016 23:50

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

NateTheGreatt 04.10.2017 00:28

Как подчеркнул Tilendor в ответе Джона Скита, потоки имеют метод CopyTo, начиная с .NET 4.

var fileStream = File.Create("C:\\Path\\To\\File");
myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
myOtherObject.InputStream.CopyTo(fileStream);
fileStream.Close();

Или с синтаксисом using:

using (var fileStream = File.Create("C:\\Path\\To\\File"))
{
    myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
    myOtherObject.InputStream.CopyTo(fileStream);
}

Обратите внимание, что вам нужно вызвать myOtherObject.InputStream.Seek(0, SeekOrigin.Begin), если вы еще не в начале, иначе вы не будете копировать весь поток.

Steve Rukuts 22.03.2012 16:00

Если этот входной поток получен из HTTP-соединения, он будет буферизироваться и загружаться, а затем записывать все байты из источника ?????

dbw 04.01.2014 18:16

Я создал программу просмотра PDF, в которой я использую поток. Как только я привяжу поток и сохраню файл PDF, используя тот же поток, без использования «Seek (0, SeekOrigin.Begin)», я не смогу сохранить правильный документ. так что +1 за упоминание об этом "Seek (0, SeekOrigin.Begin)"

user2463514 06.03.2014 16:28

myOtherObject.InputStream.CopyTo (fileStream); эта строка выдает ошибку: доступ запрещен.

sulhadin 29.06.2016 16:05

@sulhadin это просто означает, что у вас нет разрешения на запись на fileStream

Antoine Leclair 29.06.2016 20:05
//If you don't have .Net 4.0  :)

public void SaveStreamToFile(Stream stream, string filename)
{  
   using(Stream destination = File.Create(filename))
      Write(stream, destination);
}

//Typically I implement this Write method as a Stream extension method. 
//The framework handles buffering.

public void Write(Stream from, Stream to)
{
   for(int a = from.ReadByte(); a != -1; a = from.ReadByte())
      to.WriteByte( (byte) a );
}

/*
Note, StreamReader is an IEnumerable<Char> while Stream is an IEnumbable<byte>.
The distinction is significant such as in multiple byte character encodings 
like Unicode used in .Net where Char is one or more bytes (byte[n]). Also, the
resulting translation from IEnumerable<byte> to IEnumerable<Char> can loose bytes
or insert them (for example, "\n" vs. "\r\n") depending on the StreamReader instance
CurrentEncoding.
*/

Побайтовое копирование потока (с использованием ReadByte / WriteByte) будет намного медленнее, чем копирование буфера за буфером (с использованием Read (byte [], int, int) / Write (byte [], int, int)).

Kevin 10.08.2011 07:10
public void testdownload(stream input)
{
    byte[] buffer = new byte[16345];
    using (FileStream fs = new FileStream(this.FullLocalFilePath,
                        FileMode.Create, FileAccess.Write, FileShare.None))
    {
        int read;
        while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
        {
             fs.Write(buffer, 0, read);
        }
    }
}

Подача буферизованного входного потока прямо на FileStream - хорошо!

vapcguy 12.10.2016 23:55
public void CopyStream(Stream stream, string destPath)
{
  using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
  {
    stream.CopyTo(fileStream);
  }
}

Вероятно, вам не стоит помещать объект stream в скобку using(){}. Ваш метод не создавал поток, поэтому он не должен его удалять.

LarsTech 02.08.2013 01:16

Вместо этого вам нужно использовать FileStream, иначе он будет открыт до тех пор, пока не будет собран сборщиком мусора.

Pavel Chikulaev 17.01.2014 03:55

Все прошло нормально, но я получил результат в 0 КБ. Вместо этого мне пришлось сделать это для правильного вывода: File.WriteAllBytes(destinationFilePath, input.ToArray());. В моем случае input - это MemoryStream, поступающий из ZipArchive.

SNag 10.01.2016 04:50
private void SaveFileStream(String path, Stream stream)
{
    var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
    stream.CopyTo(fileStream);
    fileStream.Dispose();
}

Все прошло нормально, но я получил результат в 0 КБ. Вместо этого мне пришлось сделать это для правильного вывода: File.WriteAllBytes(destinationFilePath, input.ToArray());. В моем случае input - это MemoryStream, поступающий из ZipArchive.

SNag 10.01.2016 04:50

Это помогло мне понять, что я делаю не так. Однако не забудьте перейти в начало потока: stream.Seek(0, SeekOrigin.Begin);

Nathan Bills 09.08.2016 01:06

Другой вариант - передать поток на byte[] и использовать File.WriteAllBytes. Это должно делать:

using (var stream = new MemoryStream())
{
    input.CopyTo(stream);
    File.WriteAllBytes(file, stream.ToArray());
}

Оборачивание его в метод расширения дает ему лучшее именование:

public void WriteTo(this Stream input, string file)
{
    //your fav write method:

    using (var stream = File.Create(file))
    {
        input.CopyTo(stream);
    }

    //or

    using (var stream = new MemoryStream())
    {
        input.CopyTo(stream);
        File.WriteAllBytes(file, stream.ToArray());
    }

    //whatever that fits.
}

Если ввод слишком велик, вы получите исключение нехватки памяти. Вариант копирования содержимого из входного потока в файловый поток намного лучше

Ykok 04.09.2015 14:02

Я не получаю все ответы, используя CopyTo, где, возможно, системы, использующие приложение, могли не быть обновлены до .NET 4.0+. Я знаю, что некоторые хотели бы заставить людей обновиться, но совместимость тоже хороша.

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

byte[] bytes = myOtherObject.InputStream.ToArray();

Получив байты, вы можете легко записать их в файл:

public static void WriteFile(string fileName, byte[] bytes)
{
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (!path.EndsWith(@"\")) path += @"\";

    if (File.Exists(Path.Combine(path, fileName)))
        File.Delete(Path.Combine(path, fileName));

    using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write))
    {
        fs.Write(bytes, 0, (int)bytes.Length);
        //fs.Close();
    }
}

Этот код работает так же, как я тестировал его с файлом .jpg, хотя я признаю, что использовал его только с небольшими файлами (менее 1 МБ). Один поток, без копирования между потоками, без кодирования, просто напишите байты! Нет необходимости усложнять вещи с StreamReader, если у вас уже есть поток, который вы можете преобразовать в bytes напрямую с .ToArray()!

Единственные потенциальные недостатки, которые я вижу в этом способе, - это наличие у вас большого файла, использование его в виде потока и использование .CopyTo() или его эквивалента позволяет FileStream передавать его в потоковом режиме вместо использования массива байтов и чтения байтов один за другим. В результате это может быть медленнее. Но он не должен задыхаться, поскольку метод .Write() в FileStream обрабатывает запись байтов и делает это только по одному байту за раз, поэтому он не забивает память, за исключением у вас должно быть достаточно памяти для хранения потока как объекта byte[]. В моей ситуации, когда я использовал это, получая OracleBlob, мне пришлось перейти к byte[], он был достаточно маленьким, и, кроме того, мне все равно не было потоковой передачи, поэтому я просто отправил свои байты в свою функцию, описанную выше.

Другой вариант, используя поток, - это использовать его с функцией CopyStream Джона Скита, которая была в другом сообщении - здесь просто используется FileStream для получения входного потока и создания файла напрямую из него. Он не использует File.Create, как он (что сначала показалось мне проблематичным, но позже выяснилось, что это, скорее всего, просто ошибка VS ...).

/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[8 * 1024];
    int len;
    while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, len);
    }    
}

public static void WriteFile(string fileName, Stream inputStream)
{
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (!path.EndsWith(@"\")) path += @"\";

    if (File.Exists(Path.Combine(path, fileName)))
        File.Delete(Path.Combine(path, fileName));

    using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write)
    {
        CopyStream(inputStream, fs);
    }

    inputStream.Close();
    inputStream.Flush();
}

Нет необходимости звонить в Close из-за using()

Alex78191 25.06.2017 16:07

@ Alex78191 Если вы говорите о inputStream.Close(), посмотрите еще раз - inputStream отправляется как переменная. using находится в выходном потоке path+filename. Если вы говорили о fs.Close() в середине using, извините, вы были правы, и я удалил это.

vapcguy 19.07.2017 18:52

Должен смыться перед закрытием. Хотя близко тоже следует промыть.

Andrew 16.11.2020 20:16

@Andrew Я думаю, что именно поэтому я сделал их в том порядке, в котором я делал - потому что я не думаю, что вы можете выполнить .Close() в потоке, который был сброшен, потому что .Flush() тоже закрывает его, и я хотел выполнить обе команды.

vapcguy 19.11.2020 02:38

Вот пример правильного использования и реализации idisposable:

static void WriteToFile(string sourceFile, string destinationfile, bool append = true, int bufferSize = 4096)
{
    using (var sourceFileStream = new FileStream(sourceFile, FileMode.OpenOrCreate))
    {
        using (var destinationFileStream = new FileStream(destinationfile, FileMode.OpenOrCreate))
        {
            while (sourceFileStream.Position < sourceFileStream.Length)
            {
                destinationFileStream.WriteByte((byte)sourceFileStream.ReadByte());
            }
        }
    }
}

... и еще это

    public static void WriteToFile(Stream stream, string destinationFile, int bufferSize = 4096, FileMode mode = FileMode.OpenOrCreate, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.ReadWrite)
    {
        using (var destinationFileStream = new FileStream(destinationFile, mode, access, share))
        {
            while (stream.Position < stream.Length) 
            {
                destinationFileStream.WriteByte((byte)stream.ReadByte());
            }
        }
    }

Ключевым моментом является понимание правильного использования using (которое должно быть реализовано при создании экземпляра объекта, реализующего idisposable, как показано выше), и хорошее представление о том, как свойства работают для потоков. Позиция - это буквально индекс в потоке (который начинается с 0), за которым следует каждый байт, считанный с использованием метода readbyte. В этом случае я, по сути, использую его вместо переменной цикла for и просто позволяю ему пройти весь путь до длины, которая БУКВАЛЬНО является концом всего потока (в байтах). Игнорируйте в байтах, потому что это практически то же самое, и у вас будет что-то простое и элегантное, подобное этому, которое решает все чисто.

Также имейте в виду, что метод ReadByte просто преобразует байт в int в процессе и может быть просто преобразован обратно.

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

private void StreamBuffer(Stream stream, int buffer)
{
    using (var memoryStream = new MemoryStream())
    {
        stream.CopyTo(memoryStream);
        var memoryBuffer = memoryStream.GetBuffer();

        for (int i = 0; i < memoryBuffer.Length;)
        {
            var networkBuffer = new byte[buffer];
            for (int j = 0; j < networkBuffer.Length && i < memoryBuffer.Length; j++)
            {
                networkBuffer[j] = memoryBuffer[i];
                i++;
            }
            //Assuming destination file
            destinationFileStream.Write(networkBuffer, 0, networkBuffer.Length);
        }
    }
}

Объяснение довольно простое: мы знаем, что нам нужно иметь в виду весь набор данных, которые мы хотим записать, а также то, что мы хотим записать только определенные суммы, поэтому нам нужен первый цикл с последним параметром пустым (так же, как и while ). Затем мы инициализируем буфер массива байтов, для которого установлен размер переданного, и во втором цикле мы сравниваем j с размером буфера и размером исходного, и если он больше размера оригинала байтовый массив, завершите запуск.

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