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

713

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

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

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

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

Loadman
источник
1
Что такое myOtherObject?
anhtv13
2
Все еще нет принятого ответа на этот вопрос?
Бретт Ригби
@BrettRigby Есть ответ Джона Скита, он в основном принят автоматически: D
Рикардо Диас Мораис

Ответы:

913

Как подчеркнул 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);
}
Антуан Леклер
источник
67
Обратите внимание, что вам нужно позвонить, myOtherObject.InputStream.Seek(0, SeekOrigin.Begin)если вы еще не в начале или вы не будете копировать весь поток.
Стив Рукутс,
3
Если этот входной поток получен из http-соединения, он будет буферизован и загружен, а затем запишет все байты из источника ?????
DBW
2
Я создал средство просмотра PDF, в котором я использую поток, когда я связываю поток, и когда я сохраняю файл PDF, используя тот же поток, без использования «Seek (0, SeekOrigin.Begin)», я не смогу сохранить правильный документ. так что +1 за упоминание этого «Seek (0, SeekOrigin.Begin)»
user2463514
myOtherObject.InputStream.CopyTo (FileStream); эта строка выдает ошибку: доступ запрещен.
Сульхадин
2
myOtherObject ??
Гарри
531

Вы не должны использовать StreamReaderдля двоичных файлов (например, GIF-файлов или JPG). 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. Я вижу людей, которые создают байтовые массивы размером всего файла, что может вызвать проблемы для больших файлов.
Tilendor
81
@Tilendor: это присутствует как метод расширения в .NET 4. (CopyTo)
Джон Скит
33
Я не думаю, что это метод расширения, но он новый в классе Stream.
Кугель
9
@ Кугель: Ты прав, извини. Я использовал его как метод расширения в служебной библиотеке, но теперь, когда он находится в самом Stream, мой метод расширения не вызывается.
Джон Скит
4
@Florian: Это достаточно произвольно - достаточно маленькое значение, чтобы не занимать слишком много памяти, и достаточно большое, чтобы передавать разумный кусок за раз. Было бы неплохо быть 16K, может быть 32K - я просто буду осторожен, чтобы не оказаться в куче больших объектов.
Джон Скит
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();
}
jhonjairoroa87
источник
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, я должен был перейти к a 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();
}
vapcguy
источник
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 (byte [], int, int) / Write (byte [], 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 ГБ :)
Buthrakaur
1
Это не работает с ResponseStream, потому что он имеет неизвестную длину.
Томас Кубес
Хотя это правда, что вам нужно иметь доступную память для этого byte[], я думаю, что было бы редко, если бы вы передавали 1 ГБ + BLOB-объект в файл ... если у вас нет сайта, на котором есть 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);
        }
    }
}
Angelo
источник
Поставка буферизованного входного потока непосредственно в FileStream- приятно!
vapcguy
3

Вот пример, который использует правильное использование и реализацию 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(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. В этом случае я, по сути, использую его вместо переменной цикла for и просто позволяю ему следовать по всему пути вплоть до длины, которая буквально является концом всего потока (в байтах). Игнорируйте в байтах, потому что это практически то же самое, и у вас будет что-то простое и элегантное, как это, которое решает все чисто.

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

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

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 с размером буфера и размером исходного, и если он больше, чем размер исходного байтовый массив, конец цикла.

Эндрю Рэмшоу
источник