Як зберегти потік у файл у C #?


713

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

Існуючий код:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. Мені потрібно зберегти це на диску (у мене є ім'я файлу).
  2. Надалі я, можливо, захочу зберегти це на SQL Server.

У мене також є тип кодування, який мені знадобиться, якщо я зберігаю його на SQL Server, правильно?


1
Що таке мій інший об'єкт?
anhtv13

2
Все ще немає прийнятої відповіді на це питання?
Бретт Рігбі

@BrettRigby Є відповідь Джона Скіта, вона майже прийнята автоматично: D
Рікардо Діас Мораіс

Відповіді:


912

Як підкреслив Тилендор у відповіді Джона Скіта, потоки застосовують 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);
}

66
Зауважте, що вам потрібно зателефонувати, myOtherObject.InputStream.Seek(0, SeekOrigin.Begin)якщо ви ще не на початку або не скопіюєте весь потік.
Стів Рукуць

3
Якщо цей потік введення отримано з http-з'єднання, то він буде буферувати та завантажувати, а потім записувати всі байти з джерела ?????
dbw

2
Я створив переглядач PDF, де я використовую потік, як тільки я прив’язую потік і коли я зберігаю pdf-файл із використанням того ж потоку, то не використовуючи "Seek (0, SeekOrigin.Begin)", я не зможу зберегти правильний документ. тому +1 за згадування цього "Шукати (0, SeekOrigin.Begin)"
користувач2463514

myOtherObject.InputStream.CopyTo (fileStream); цей рядок дає помилку: доступ заборонений.
сульхадін

2
myOtherObject ??
Гаррі

531

Ви не повинні використовувати StreamReaderдля двійкових файлів (наприклад, gifs або jpgs). StreamReaderпризначений для текстових даних. Ви майже напевно втратите дані, якщо будете використовувати їх для довільних двійкових даних. (Якщо ви використовуєте Encoding.GetEncoding (28591), ви, ймовірно, будете в порядку, але в чому сенс?)

Для чого взагалі потрібно використовувати StreamReader? Чому б просто не зберегти двійкові дані як бінарні дані і записати їх на диск (або SQL) як бінарні дані?

EDIT: Як це , здається, що - то люди хочуть бачити ... якщо ви дійсно просто хочете скопіювати один потік в інший (наприклад , в файл) використовуйте що - щось на зразок цього:

/// <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, що служить в основному тій самій цілі.


6
Це здається настільки поширеним випадком, що я здивований, що він не в .NET Я бачу людей, які створюють байтові масиви розміром усього файлу, що може спричинити проблеми для великих файлів.
Тилендор

81
@Tilendor: Це метод розширення в .NET 4. (CopyTo)
Джон Скіт

33
Я не думаю, що це метод розширення, але він новий у класі Stream.
Кугель

9
@Kugel: Ти маєш рацію, вибач. У мене це було як метод розширення в бібліотеці утиліт, але тепер, коли він знаходиться в самій потоці, мій метод розширення не викликається.
Джон Скіт

4
@Florian: Це досить довільно - досить мале значення, щоб уникнути зайвої пам’яті, і достатньо велике, щоб перенести розумний шматок за раз. Було б чудово, щоб було 16 К, можливо, 32 К - я просто обережний, щоб не опинитися на великій купі об’єктів.
Джон Скіт

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

28
Напевно, ви не повинні ставити streamоб'єкт у using(){}дужці. Ваш метод не створив потік, тому він не повинен ним розпоряджатися.
LarsTech

2
Замість цього вам потрібно поставити FileStreamзамість використання, інакше він буде триматися відкритим, поки не буде зібрано сміття.
Павло Чикулаєв

Я виявив, що ваш підхід був набагато ближчим, щоб вирішити мою проблему в WinForms з моїм класом шлюзу AWS S3! Дякую тобі!
Луїз Едуардо

2
Це вийшло чудово, але я отримав вихід 0 Кб. Замість цього я повинен був зробити це для правильного виведення: File.WriteAllBytes(destinationFilePath, input.ToArray());. У моєму випадку input- це MemoryStreamнадходження зсередини ZipArchive.
SNag

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

1
Це вийшло чудово, але я отримав вихід 0 Кб. Замість цього я повинен був зробити це для правильного виведення: File.WriteAllBytes(destinationFilePath, input.ToArray());. У моєму випадку input- це MemoryStreamнадходження зсередини ZipArchive.
SNag

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

9

Я не отримую всіх відповідей за допомогою 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();
}

1
Не потрібно дзвонити Closeчерезusing()
Alex78191

@ Alex78191 Якщо ви говорите про це inputStream.Close(), подивіться ще раз - inputStreamнадсилається як змінна. Значення usingзнаходиться у path+filenameвихідному потоці. Якщо ви говорили про fs.Close()середину using, вибачте, ви з цим правили, і я це усунув.
vapcguy

8
//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.
*/

16
Копіювання потоку в байт (за допомогою ReadByte / WriteByte) буде набагато повільніше, ніж копіювання буфера за буфером (використовуючи Read (байт [], int, int) / Write (байт [], int, int) ".
Кевін

6

Чому б не використовувати об’єкт 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);
     }
}

46
Що робити, якщо вхідний потік становить 1 Гб - цей код намагатиметься виділити 1 ГБ буфера :)
Бутракаур

1
Це не працює з ResponseStream, оскільки він невідомої довжини.
Томаш Кубес

Хоча це правда, вам доведеться мати доступну пам'ять для byte[], але я думаю, що це буде рідкістю, щоб ви передавали в файл 1 Гб + крапку ... якщо у вас немає сайту, який зберігає торрент DVD ... Плюс , у більшості комп'ютерів є щонайменше 2 ГБ оперативної пам’яті в будь-який час. У будь-якому разі .... Caveat дійсний, але я думаю, що це випадок, коли це, мабуть, «досить добре» для більшості робочих місць.
vapcguy

Веб-сервери взагалі не переносять подібний випадок, якщо на веб-сайті не працює одночасно один користувач.
NateTheGreatt

6

Інший варіант - повернути потік до 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.
}

3
Якщо вхід занадто великий, ви втратите виняток із пам'яті. Варіант копіювання вмісту з вхідного потоку до потоку файлів значно кращий
Ykok

4
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

3

Ось приклад, який використовує належне використання та реалізацію невідступних:

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

Ключовим моментом є розуміння правильного використання використання (яке має бути реалізовано на екземплярі об'єкта, який реалізує ідентифікацію, як показано вище), а також мати гарне уявлення про те, як властивості працюють для потоків. Позиція - це буквально індекс всередині потоку (який починається з 0), який слідує, коли кожен байт читається за допомогою методу readbyte. У цьому випадку я по суті використовую його замість змінної циклу і просто даю їй пройти всю дорогу до тієї довжини, яка ЛІТЕРАЛЬНО є кінцем усього потоку (у байтах). Ігноруйте в байтах, тому що це практично те саме, і у вас вийде щось просте і елегантне, як це, що вирішує все чисто.

Майте також на увазі, що метод 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);
        }
    }
}

Пояснення досить просте: ми знаємо, що нам потрібно пам’ятати про весь набір даних, який ми хочемо записати, а також, що ми хочемо записувати лише певні суми, тому ми хочемо, щоб перший цикл з останнім параметром був порожнім (те саме, що і під час ). Далі ми ініціалізуємо буфер масиву байтів, який встановлюється на розмір переданого, і за допомогою другого циклу порівнюємо j з розміром буфера і розміром вихідного, і якщо він більший за розмір оригіналу байтовий масив, закінчити пробіг.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.