Come faccio a salvare uno stream in un file in C #?


713

Ho un StreamReaderoggetto che ho inizializzato con un flusso, ora voglio salvare questo flusso su disco (il flusso può essere un .gifo .jpgo .pdf).

Codice esistente:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. Devo salvarlo su disco (ho il nome file).
  2. In futuro potrei voler archiviare questo su SQL Server.

Ho anche il tipo di codifica, che sarà necessario se lo memorizzo su SQL Server, corretto?


1
Che cos'è myOtherObject?
anhtv13

2
Ancora nessuna risposta accettata su questa domanda?
Brett Rigby,

@BrettRigby C'è una risposta di Jon Skeet, è praticamente accettata automaticamente: D
Ricardo Dias Morais

Risposte:


913

Come sottolineato da Tilendor nella risposta di Jon Skeet, i flussi hanno un CopyTometodo da .NET 4.

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

O con la usingsintassi:

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

67
Nota che devi chiamare myOtherObject.InputStream.Seek(0, SeekOrigin.Begin)se non sei già all'inizio o non copi l'intero stream.
Steve Rukuts,

3
Se questo flusso di input viene ottenuto dalla connessione http, bufferizzerà e scaricherà e quindi scriverà tutti i byte dalla sorgente ?????
dbw,

2
Ho creato il visualizzatore PDF dove utilizzo lo stream, una volta associato lo stream e quando salvo il file pdf utilizzando lo stesso stream, quindi senza utilizzare "Seek (0, SeekOrigin.Begin)" Non sarò in grado di salvare il documento corretto. quindi +1 per menzionare questo "Seek (0, SeekOrigin.Begin)"
user2463514

myOtherObject.InputStream.CopyTo (FileStream); questa riga fornisce un errore: accesso negato.
sulhadin,

2
myOtherObject ??
Harry,

531

Non è necessario utilizzare StreamReaderper file binari (come gif o jpg). StreamReaderè per i dati di testo . Quasi sicuramente perderete i dati se li utilizzate per dati binari arbitrari. (Se usi Encoding.GetEncoding (28591) probabilmente starai bene, ma qual è il punto?)

Perché devi usare StreamReadera? Perché non conservare semplicemente i dati binari come dati binari e riscriverli su disco (o SQL) come dati binari?

EDIT: Come questo sembra essere qualcosa che la gente vuole vedere ... se non volete semplicemente copiare un flusso a un altro (ad esempio, per un file) qualcosa di utilizzo simile a questo:

/// <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);
    }    
}

Per usarlo per scaricare un flusso in un file, ad esempio:

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

Si noti che è Stream.CopyTostato introdotto in .NET 4 e ha sostanzialmente lo stesso scopo.


6
Sembra un caso così comune, sono sorpreso che non sia in .NET. Vedo che le persone che creano array di byte hanno le dimensioni dell'intero file, il che può causare problemi per file di grandi dimensioni.
Tilendor,

81
@Tilendor: è presente come metodo di estensione in .NET 4. (CopyTo)
Jon Skeet,

33
Non penso che sia un metodo di estensione, ma è nuovo nella classe Stream.
Kugel,

9
@Kugel: hai ragione, scusa. L'ho avuto come metodo di estensione in una libreria di utilità, ma ora che si trova nello stesso Stream, il mio metodo di estensione non viene chiamato.
Jon Skeet,

4
@Florian: è ragionevolmente arbitrario - un valore abbastanza piccolo da evitare di prendere troppa memoria e abbastanza grande da trasferire un pezzo ragionevole alla volta. Sarebbe bello essere 16K, forse 32K - starei solo attento a non finire sul mucchio di oggetti di grandi dimensioni.
Jon Skeet,

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

28
Probabilmente non dovresti mettere l' streamoggetto tra using(){}parentesi. Il tuo metodo non ha creato lo stream, quindi non dovrebbe eliminarlo.
LarsTech,

2
Invece è necessario mettere FileStreaminvece a utilizzare, altrimenti rimarrà aperto fino a quando non sarà raccolto.
Pavel Chikulaev,

Ho scoperto che il tuo approccio è stato molto più vicino per risolvere il mio problema in WinForms con la mia classe gateway classe AWS S3! grazie!
Luiz Eduardo,

2
Questo ha funzionato bene ma ho ottenuto un output di 0 KB. Invece ho dovuto fare questo per l'uscita corretta: File.WriteAllBytes(destinationFilePath, input.ToArray());. Nel mio caso, inputè una MemoryStreamproveniente dall'interno di una 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
Questo ha funzionato bene ma ho ottenuto un output di 0 KB. Invece ho dovuto fare questo per l'uscita corretta: File.WriteAllBytes(destinationFilePath, input.ToArray());. Nel mio caso, inputè una MemoryStreamproveniente dall'interno di una ZipArchive.
SNag

2
Questo mi ha aiutato a capire cosa stavo facendo di sbagliato. Tuttavia, non dimenticare di spostarti all'inizio del flusso: stream.Seek(0, SeekOrigin.Begin);
Nathan Bills

9

Non ottengo tutte le risposte usando CopyTo, dove forse i sistemi che utilizzano l'app potrebbero non essere stati aggiornati a .NET 4.0+. So che alcuni vorrebbero costringere le persone ad aggiornare, ma anche la compatibilità è buona.

Un'altra cosa, in primo luogo non riesco a utilizzare uno stream per copiare da un altro stream. Perché non fare solo:

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

Una volta che hai i byte, puoi facilmente scriverli in un file:

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

Questo codice funziona come l'ho testato con un .jpgfile, anche se ammetto di averlo usato solo con file piccoli (meno di 1 MB). Un flusso, nessuna copia tra i flussi, nessuna codifica necessaria, basta scrivere i byte! Non c'è bisogno di complicare eccessivamente le cose StreamReaderse hai già un flusso con cui puoi convertirlo bytesdirettamente .ToArray()!

Solo i potenziali svantaggi che posso vedere nel farlo in questo modo è se c'è un file di grandi dimensioni che hai, averlo come flusso e l'utilizzo .CopyTo()o equivalente consente FileStreamdi eseguire lo streaming invece di utilizzare un array di byte e leggere i byte uno per uno. Di conseguenza, potrebbe essere più lento farlo in questo modo. Ma non dovrebbe soffocare dal momento che il .Write()metodo degli FileStreamhandle scrive i byte e lo sta facendo solo un byte alla volta, quindi non ostruirà la memoria, tranne per il fatto che dovrai avere memoria sufficiente per contenere lo stream come byte[]oggetto . Nella mia situazione in cui l'ho usato, ottenendo un OracleBlob, dovevo andare a un byte[], era abbastanza piccolo e, inoltre, non c'era streaming disponibile per me, quindi ho appena inviato i miei byte alla mia funzione, sopra.

Un'altra opzione, usando un flusso, sarebbe quella di usarlo con la CopyStreamfunzione di Jon Skeet che era in un altro post - questo usa solo FileStreamper prendere il flusso di input e creare il file direttamente da esso. Non usa File.Create, come ha fatto lui (che inizialmente mi è sembrato problematico, ma in seguito ha scoperto che probabilmente era solo un bug 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
Non è necessario chiamare a Closecausa diusing()
Alex78191,

@ Alex78191 Se stai parlando inputStream.Close(), guarda di nuovo - inputStreamviene inviato come variabile. Il usinginizia il path+filenameflusso di output. Se stavi parlando fs.Close()nel mezzo del using, scusa, avevi ragione su questo e l'ho rimosso.
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
Copiare uno stream byte per byte (usando ReadByte / WriteByte) sarà molto più lento della copia buffer per buffer (usando Read (byte [], int, int) / Write (byte [], int, int)).
Kevin,

6

Perché non usare un oggetto 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
cosa succede se il flusso di input è lungo 1 GB - questo codice tenterebbe di allocare buffer da 1 GB :)
Buthrakaur,

1
Questo non funziona con ResponseStream, perché è di lunghezza sconosciuta.
Tomas Kubes,

Anche se è vero che dovresti avere la memoria disponibile per byte[], penso che sarebbe raro che tu stia trasmettendo in streaming un BLOB da 1 GB + a un file ... a meno che tu non abbia un sito che mantiene i torrent DVD ... Plus , la maggior parte dei computer ha almeno 2 GB di RAM disponibili in questi giorni, comunque .... Caveat è valido, ma penso che questo sia un caso in cui probabilmente è "abbastanza buono" per la maggior parte dei lavori.
vapcguy,

I server web non tollereranno affatto un caso come questo, a meno che il sito Web non abbia un solo utente attivo contemporaneamente.
NateTheGreatt,

6

Un'altra opzione è quella di ottenere lo stream byte[]e utilizzare File.WriteAllBytes. Questo dovrebbe fare:

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

Avvolgendolo in un metodo di estensione si ottiene una migliore denominazione:

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
Se l'input è troppo grande, si otterrà un'eccezione di memoria insufficiente. L'opzione di copiare il contenuto dal flusso di input in un filestream è molto meglio
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);
        }
    }
}

Fornire un flusso di input bufferizzato direttamente a FileStream- nice!
vapcguy,

3

Ecco un esempio che utilizza usi corretti e implementazione di 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());
            }
        }
    }
}

... e c'è anche questo

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

La chiave è comprendere l'uso corretto dell'uso (che dovrebbe essere implementato sull'istanza dell'oggetto che implementa idisposable come mostrato sopra) e avere una buona idea di come funzionano le proprietà per i flussi. La posizione è letteralmente l'indice all'interno del flusso (che inizia da 0) che viene seguito quando ogni byte viene letto usando il metodo readbyte. In questo caso lo sto essenzialmente usando al posto di una variabile for loop e sto semplicemente lasciando che segua tutto il percorso fino alla lunghezza che è LETTERAMENTE la fine dell'intero flusso (in byte). Ignora in byte perché è praticamente lo stesso e avrai qualcosa di semplice ed elegante come questo che risolve tutto in modo pulito.

Tieni presente, inoltre, che il metodo ReadByte semplicemente lancia il byte in un int nel processo e può essere semplicemente riconvertito.

Aggiungerò un'altra implementazione che ho scritto di recente per creare una sorta di buffer dinamico per garantire scritture sequenziali di dati per prevenire un sovraccarico massiccio

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

La spiegazione è abbastanza semplice: sappiamo che dobbiamo tenere a mente l'intero insieme di dati che desideriamo scrivere e anche che vogliamo solo scrivere determinate quantità, quindi vogliamo che il primo ciclo con l'ultimo parametro vuoto (lo stesso mentre ). Successivamente, inizializziamo un buffer di array di byte che è impostato sulla dimensione di ciò che è passato e con il secondo loop confrontiamo j con la dimensione del buffer e la dimensione di quello originale, e se è maggiore della dimensione dell'originale array di byte, termina l'esecuzione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.