¿Cómo guardo una secuencia en un archivo en C #?

713

Tengo un StreamReaderobjeto que inicialicé con una secuencia, ahora quiero guardar esta secuencia en el disco (la secuencia puede ser una .gifo .jpgo .pdf).

Código existente:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. Necesito guardar esto en el disco (tengo el nombre del archivo).
  2. En el futuro, es posible que desee almacenar esto en SQL Server.

También tengo el tipo de codificación, que necesitaré si lo almaceno en SQL Server, ¿correcto?

Loadman
fuente
1
¿Qué es myOtherObject?
anhtv13
2
¿Aún no acepta una respuesta a esta pregunta?
Brett Rigby
@BrettRigby Hay una respuesta de Jon Skeet, es bastante autoaceptada: D
Ricardo Dias Morais

Respuestas:

913

Como lo destacó Tilendor en la respuesta de Jon Skeet, las transmisiones tienen un CopyTométodo desde .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 usingsintaxis:

using (var fileStream = File.Create("C:\\Path\\To\\File"))
{
    myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
    myOtherObject.InputStream.CopyTo(fileStream);
}
Antoine Leclair
fuente
67
Tenga en cuenta que debe llamar myOtherObject.InputStream.Seek(0, SeekOrigin.Begin)si aún no está al principio o si no copiará toda la transmisión.
Steve Rukuts
3
Si este flujo de entrada se obtiene de la conexión http, ¿se almacenará en búfer y se descargará y luego se escribirán todos los bytes de la fuente?
dbw
2
He creado el visor de PDF donde estoy usando la secuencia, una vez que la ato y cuando guardo el archivo pdf usando la misma secuencia y luego sin usar "Buscar (0, SeekOrigin.Begin)" no podré guardar el documento correcto. entonces +1 por mencionar este "Seek (0, SeekOrigin.Begin)"
usuario2463514
myOtherObject.InputStream.CopyTo (fileStream); esta línea da un error: acceso denegado.
sulhadin
2
myOtherObject ??
Harry
531

No debe usar StreamReaderarchivos binarios (como gifs o jpgs). StreamReaderes para datos de texto . Es casi ciertamente pérdidas de datos si lo usa para datos binarios arbitrarios. (Si usa Encoding.GetEncoding (28591) probablemente estará bien, pero ¿cuál es el punto?)

¿Por qué necesitas usar un StreamReader? ¿Por qué no simplemente mantener los datos binarios como datos binarios y volver a escribirlos en el disco (o SQL) como datos binarios?

EDIT: Como esto parece ser algo que la gente quiere ver ... si usted no sólo quiere copiar una secuencia a otra (por ejemplo, a un archivo) usar algo como esto:

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

Para usarlo para volcar una secuencia en un archivo, por ejemplo:

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

Tenga en cuenta que Stream.CopyTose introdujo en .NET 4, que sirve básicamente para el mismo propósito.

Jon Skeet
fuente
66
Parece un caso tan común que me sorprende que no esté en .NET. Veo personas creando conjuntos de bytes del tamaño de todo el archivo, lo que puede causar problemas para archivos grandes.
Tilendor
81
@Tilendor: está presente como un método de extensión en .NET 4. (CopyTo)
Jon Skeet
33
No creo que sea un método de extensión, pero es nuevo en la clase Stream.
Kugel
99
@ Kugel: Tienes razón, lo siento. Lo tenía como método de extensión en una biblioteca de utilidades, pero ahora que está en Stream, no se llama a mi método de extensión.
Jon Skeet
44
@Florian: es razonablemente arbitrario: un valor lo suficientemente pequeño como para evitar tomar demasiada memoria y lo suficientemente grande como para transferir una porción razonable a la vez. Sería bueno tener 16K, 32K tal vez, solo tendría cuidado de no terminar en el montón de objetos grandes.
Jon Skeet
77
public void CopyStream(Stream stream, string destPath)
{
  using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
  {
    stream.CopyTo(fileStream);
  }
}
Darren Corbett
fuente
28
Probablemente no deberías poner el streamobjeto entre using(){}corchetes. Su método no creó la secuencia, por lo que no debería deshacerse de ella.
LarsTech
2
En su FileStreamlugar, debe utilizarlo, de lo contrario, se mantendrá abierto hasta que se recolecte la basura.
Pavel Chikulaev
¡He descubierto que su enfoque estaba mucho más cerca de resolver mi problema en WinForms con mi clase de puerta de enlace de clase AWS S3! ¡gracias!
Luiz Eduardo
2
Esto funcionó bien pero obtuve una salida de 0 KB. En vez tuve que hacer esto para la salida correcta: File.WriteAllBytes(destinationFilePath, input.ToArray());. En mi caso, inputes una MemoryStreamque viene de dentro de 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();
}
jhonjairoroa87
fuente
1
Esto funcionó bien pero obtuve una salida de 0 KB. En vez tuve que hacer esto para la salida correcta: File.WriteAllBytes(destinationFilePath, input.ToArray());. En mi caso, inputes una MemoryStreamque viene de dentro de una ZipArchive.
SNag
2
Esto me ayudó a descubrir qué estaba haciendo mal. Sin embargo, no olvide pasar al comienzo de la transmisión: stream.Seek(0, SeekOrigin.Begin);
Nathan Bills
9

No obtengo todas las respuestas usando CopyTo, donde quizás los sistemas que usan la aplicación podrían no haberse actualizado a .NET 4.0+. Sé que a algunos les gustaría obligar a las personas a actualizar, pero la compatibilidad también es agradable.

Otra cosa, no consigo usar una secuencia para copiar desde otra secuencia en primer lugar. ¿Por qué no simplemente hacer:

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

Una vez que tenga los bytes, puede escribirlos fácilmente en un archivo:

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

Este código funciona como lo he probado con un .jpgarchivo, aunque admito que solo lo he usado con archivos pequeños (menos de 1 MB). Una secuencia, sin copiar entre secuencias, sin codificación, ¡solo escriba los bytes! ¡No es necesario complicar demasiado las cosas StreamReadersi ya tiene una transmisión a la que puede convertir bytesdirectamente .ToArray()!

Las únicas desventajas potenciales que puedo ver al hacerlo de esta manera es si hay un archivo grande que tiene, tenerlo como una secuencia y usar .CopyTo()o equivalente permite FileStreamtransmitirlo en lugar de usar una matriz de bytes y leer los bytes uno por uno. Puede ser más lento hacerlo de esta manera, como resultado. Pero no debería ahogarse ya que el .Write()método de los FileStreammanejadores escribe los bytes, y solo lo hace un byte a la vez, por lo que no obstruirá la memoria, excepto que tendrá que tener suficiente memoria para mantener la secuencia como byte[]objeto . En mi situación en la que usé esto, obteniendo un OracleBlob, tuve que ir a un byte[], era lo suficientemente pequeño, y además, no había transmisión disponible para mí, de todos modos, así que simplemente envié mis bytes a mi función, arriba.

Otra opción, usar un flujo, sería usarlo con la CopyStreamfunción de Jon Skeet que estaba en otra publicación; esto solo se usa FileStreampara tomar el flujo de entrada y crear el archivo directamente. No se usa File.Create, como lo hizo (lo que inicialmente parecía ser problemático para mí, pero luego descubrí que probablemente era solo un error 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
fuente
1
No es necesario llamar Closeporusing()
Alex78191
@ Alex78191 Si está hablando inputStream.Close(), mire de nuevo: inputStreamse envía como una variable. El usingestá en la path+filenamesecuencia de salida. Si estabas hablando fs.Close()en medio de using, lo siento, estabas en lo correcto y lo eliminé.
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.
*/
Jorge
fuente
16
Copiar una secuencia byte por byte (usando ReadByte / WriteByte) será mucho más lento que copiar buffer-by-buffer (usando Read (byte [], int, int) / Write (byte [], int, int)).
Kevin
6

¿Por qué no usar un objeto 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);
     }
}
Adrian
fuente
46
¿Qué pasa si el flujo de entrada tiene 1 GB de longitud? Este código intentaría asignar 1 GB de búfer :)
Buthrakaur
1
Esto no funciona con ResponseStream, porque tiene una longitud desconocida.
Tomas Kubes
Si bien es cierto que tendrías que tener la memoria disponible para el byte[], creo que sería raro que transmitieras un blob de 1 GB + a un archivo ... a menos que tengas un sitio que mantenga torrents de DVD ... Además , la mayoría de las computadoras tienen al menos 2 GB de RAM disponibles en estos días, de todos modos ... La advertencia es válida, pero creo que este es un caso en el que probablemente sea "lo suficientemente bueno" para la mayoría de los trabajos.
vapcguy
Los servidores web no tolerarán un caso como este muy bien, a menos que el sitio web solo tenga un solo usuario activo a la vez.
NateTheGreatt
6

Otra opción es llevar la transmisión a ay byte[]usarla File.WriteAllBytes. Esto debería hacer:

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

Envolverlo en un método de extensión le da un mejor nombre:

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.
}
nawfal
fuente
3
Si la entrada es demasiado grande, obtendrá una excepción de falta de memoria. La opción de copiar contenido del flujo de entrada a un flujo de archivos es mucho mejor
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
fuente
Suministro de una secuencia de entrada almacenada directamente al FileStream- ¡bien!
vapcguy
3

Aquí hay un ejemplo que usa los usos adecuados y la implementación de 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());
            }
        }
    }
}

... y también hay esto

    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 clave es comprender el uso adecuado del uso (que debe implementarse en la creación de instancias del objeto que implementa idisposable como se muestra arriba) y tener una buena idea de cómo funcionan las propiedades para las secuencias. La posición es literalmente el índice dentro de la secuencia (que comienza en 0) que se sigue a medida que se lee cada byte utilizando el método readbyte. En este caso, esencialmente lo uso en lugar de una variable de bucle for y simplemente dejo que siga hasta la longitud que es LITERALMENTE el final de toda la secuencia (en bytes). Ignora en bytes porque es prácticamente lo mismo y tendrás algo simple y elegante como este que resuelve todo limpiamente.

Tenga en cuenta también que el método ReadByte simplemente convierte el byte a un int en el proceso y simplemente puede volver a convertirlo.

Agregaré otra implementación que escribí recientemente para crear una especie de búfer dinámico que garantice la escritura secuencial de datos para evitar una sobrecarga masiva

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 explicación es bastante simple: sabemos que debemos tener en cuenta todo el conjunto de datos que deseamos escribir y también que solo queremos escribir ciertas cantidades, por lo que queremos que el primer bucle con el último parámetro esté vacío (igual que while ) A continuación, inicializamos un búfer de matriz de bytes que se establece en el tamaño de lo que se pasa, y con el segundo bucle comparamos j con el tamaño del búfer y el tamaño del original, y si es mayor que el tamaño del original conjunto de bytes, finaliza la ejecución.

Andrew Ramshaw
fuente