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

645

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

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

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

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

Теги:
sql-server
stream

10 ответов

758

Как указано в ответах Tilendor в Jon Skeet, потоки имеют метод 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);
}
  • 58
    Обратите внимание, что вам нужно вызвать myOtherObject.InputStream.Seek(0, SeekOrigin.Begin) если вы еще не в начале или не скопируете весь поток.
  • 3
    Если этот входной поток получен из http-соединения, он буферизируется и загружается, а затем записывает все байты из источника ?????
Показать ещё 4 комментария
506

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

  • 5
    Это похоже на такой распространенный случай, я удивлен, что его нет в .NET. Я вижу людей, создающих байтовые массивы размером всего файла, что может вызвать проблемы для больших файлов.
  • 76
    @Tilendor: он присутствует как метод расширения в .NET 4. (CopyTo)
Показать ещё 14 комментариев
64
public void CopyStream(Stream stream, string destPath)
{
  using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
  {
    stream.CopyTo(fileStream);
  }
}
  • 24
    Вы, вероятно, не должны помещать объект stream в скобку using(){} . Ваш метод не создал поток, поэтому он не должен избавляться от него.
  • 2
    Вместо этого вам нужно вместо этого использовать FileStream , иначе он будет оставаться открытым до тех пор, пока не будет собран мусор.
Показать ещё 3 комментария
21
private void SaveFileStream(String path, Stream stream)
{
    var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
    stream.CopyTo(fileStream);
    fileStream.Dispose();
}
  • 0
    Это работало нормально, но я получил вывод 0 КБ. Вместо этого мне пришлось сделать это для правильного вывода: File.WriteAllBytes(destinationFilePath, input.ToArray()); , В моем случае input - это MemoryStream поступающий из ZipArchive .
  • 2
    Это помогло мне понять, что я делаю неправильно. Однако не забудьте перейти к началу потока: stream.Seek(0, SeekOrigin.Begin);
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)).
7

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

Другой вариант, использующий поток, состоит в том, чтобы использовать его с функцией Jon Skeet 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()
  • 0
    @ Alex78191 Если вы говорите о inputStream.Close() , посмотрите еще раз - inputStream отправляется как переменная. using находится в path+filename выходной поток path+filename . Если вы говорили о fs.Close() во время using , извините, вы были правы, и я удалил это.
5

Почему бы не использовать объект 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 ГБ :)
  • 4
    Бутракаур прав - этот ответ должен быть удален.
Показать ещё 3 комментария
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);
        }
    }
}
  • 0
    Поставка буферизованного входного потока непосредственно в FileStream - приятно!
3

Другой вариант - передать поток в 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
    Если ввод слишком велик, вы получите исключение нехватки памяти. Возможность копирования содержимого из входного потока в файловый поток намного лучше
0

public void SaveStreamToFile(string fileFullPath, Stream stream) { File.WriteAllBytes(fileFullPath, stream.ToArray()); }

Ещё вопросы

Сообщество Overcoder
Наверх
Меню