Как сохранить поток в файл в C #? - PullRequest
651 голосов
/ 04 января 2009

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

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

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

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

Ответы [ 9 ]

842 голосов
/ 01 апреля 2011

Как подчеркнул 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);
}
495 голосов
/ 04 января 2009

Вы не должны использовать 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, служа в основном той же цели.

66 голосов
/ 02 августа 2013
public void CopyStream(Stream stream, string destPath)
{
  using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
  {
    stream.CopyTo(fileStream);
  }
}
22 голосов
/ 11 декабря 2013
private void SaveFileStream(String path, Stream stream)
{
    var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
    stream.CopyTo(fileStream);
    fileStream.Dispose();
}
10 голосов
/ 12 октября 2016

Я не получаю ответы на все вопросы, используя 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();
}
8 голосов
/ 07 июля 2011
//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.
*/
5 голосов
/ 24 апреля 2010

Почему бы не использовать объект 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);
     }
}
4 голосов
/ 13 февраля 2014

Другой вариант - получить поток на 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.
}
4 голосов
/ 16 июля 2013
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);
        }
    }
}
...