Como faço para salvar um fluxo em um arquivo em c #?

713

Eu tenho um StreamReaderobjeto que eu inicializei com um fluxo, agora quero salvar esse fluxo no disco (o fluxo pode ser um .gifou .jpgou.pdf ).

Código existente:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. Eu preciso salvar isso em disco (eu tenho o nome do arquivo).
  2. No futuro, talvez eu queira armazenar isso no SQL Server.

Também tenho o tipo de codificação, necessário para armazená-lo no SQL Server, correto?

Loadman
fonte
1
O que é myOtherObject?
precisa saber é
2
Ainda não há resposta aceita para esta pergunta?
Brett Rigby
@BrettRigby Há uma resposta de Jon Skeet, é praticamente aceita automaticamente: D
Ricardo Dias Morais

Respostas:

913

Conforme destacado por Tilendor na resposta de Jon Skeet, os fluxos têm um CopyTométodo desde o .NET 4.

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

Ou com a usingsintaxe:

using (var fileStream = File.Create("C:\\Path\\To\\File"))
{
    myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
    myOtherObject.InputStream.CopyTo(fileStream);
}
Antoine Leclair
fonte
67
Observe que você deve ligar myOtherObject.InputStream.Seek(0, SeekOrigin.Begin)se ainda não estiver no início ou se não copiará o fluxo inteiro.
Steve Rukuts 22/03/12
3
Se esse fluxo de entrada for obtido da conexão http, ele fará o buffer e o download e, em seguida, gravará todos os bytes da fonte ?????
dbw
2
Eu criei o visualizador de PDF onde estou usando o stream, depois de vincular o stream e quando eu salvar o arquivo pdf usando o mesmo stream e sem usar "Seek (0, SeekOrigin.Begin)", não será possível salvar o documento correto. então marque +1 por mencionar isso "Seek (0, SeekOrigin.Begin)"
user2463514
myOtherObject.InputStream.CopyTo (fileStream); esta linha apresenta um erro: acesso negado.
sulhadin
2
myOtherObject ??
Harry
531

Você não deve usar StreamReaderpara arquivos binários (como gifs ou jpgs). StreamReaderé para dados de texto . Você quase certamente perderá dados se usá-los para dados binários arbitrários. (Se você usar Encoding.GetEncoding (28591), provavelmente ficará bem, mas qual é o objetivo?

Por que você precisa usar um StreamReader? Por que não apenas manter os dados binários como dados binários e gravá-los no disco (ou SQL) como dados binários?

EDIT: Como este parece ser algo que as pessoas querem ver ... se você não apenas quer copiar um fluxo para outro (por exemplo, para um arquivo) usar algo como isto:

/// <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 usá-lo para despejar um fluxo em um arquivo, por exemplo:

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

Observe que Stream.CopyTofoi introduzido no .NET 4, atendendo basicamente ao mesmo objetivo.

Jon Skeet
fonte
6
Parece um caso tão comum que estou surpreso por não estar no .NET. Vejo pessoas criando matrizes de bytes do tamanho de todo o arquivo, o que pode causar problemas para arquivos grandes.
Tilendor
81
@Tilendor: É presente como um método de extensão em .NET 4. (CopyTo)
Jon Skeet
33
Não acho que seja um método de extensão, mas é novo na classe Stream.
24511 Kugel
9
@Kugel: Você está certo, desculpe. Eu o tinha como método de extensão em uma biblioteca de utilitários, mas agora que está no próprio Stream, meu método de extensão não é chamado.
Jon Skeet
4
@ Florian: É razoavelmente arbitrário - um valor pequeno o suficiente para evitar o excesso de memória e grande o suficiente para transferir uma parte razoável de cada vez. Seria bom ter 16K, 32K talvez - eu seria cuidadoso para não acabar no monte 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
fonte
28
Você provavelmente não deve colocar o streamobjeto no using(){}suporte. Seu método não criou o fluxo, portanto não deve descartá-lo.
LarsTech
2
Em vez disso, você precisa FileStreamusar, caso contrário, ele será mantido aberto até que seja coletado o lixo.
Pavel Chikulaev
Descobri que sua abordagem estava muito mais próxima de resolver meu problema no WinForms com minha classe de gateway de classe do AWS S3! obrigado!
Luiz Eduardo
2
Isso funcionou bem, mas obtive uma saída de 0 KB. Em vez disso eu tinha que fazer isso para a saída correta: File.WriteAllBytes(destinationFilePath, input.ToArray());. No meu caso, inputé uma MemoryStreamvinda de dentro de um ZipArchive.
Senão
23
private void SaveFileStream(String path, Stream stream)
{
    var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
    stream.CopyTo(fileStream);
    fileStream.Dispose();
}
jhonjairoroa87
fonte
1
Isso funcionou bem, mas obtive uma saída de 0 KB. Em vez disso eu tinha que fazer isso para a saída correta: File.WriteAllBytes(destinationFilePath, input.ToArray());. No meu caso, inputé uma MemoryStreamvinda de dentro de um ZipArchive.
Senão
2
Isso me ajudou a descobrir o que estava fazendo de errado. No entanto, não se esqueça de ir para o início do stream: stream.Seek(0, SeekOrigin.Begin);
Nathan Bills
9

Não recebo todas as respostas usando CopyTo , onde talvez os sistemas que usam o aplicativo talvez não tenham sido atualizados para o .NET 4.0+. Sei que alguns gostariam de forçar as pessoas a atualizar, mas a compatibilidade também é boa.

Outra coisa, eu não uso um fluxo para copiar de outro fluxo em primeiro lugar. Por que não fazer:

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

Depois de ter os bytes, você pode gravá-los facilmente em um arquivo:

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

Esse código funciona como eu testei com um .jpgarquivo, embora admita que o usei apenas com arquivos pequenos (menos de 1 MB). Um fluxo, sem copiar entre fluxos, sem necessidade de codificação, basta escrever os bytes! Não há necessidade de complicar demais as coisas StreamReaderse você já tiver um fluxo com o qual possa converter bytesdiretamente .ToArray()!

Somente possíveis desvantagens que posso ver dessa maneira é que se você tiver um arquivo grande, tê-lo como um fluxo e usar .CopyTo()ou equivalente permite FileStreamtransmiti-lo em vez de usar uma matriz de bytes e ler os bytes um a um. Como resultado, pode ser mais lento fazê-lo dessa maneira. Mas não deve engasgar, já que o .Write()método dos FileStreammanipuladores escreve os bytes e apenas o executa um byte de cada vez, para que não obstrua a memória, exceto que você terá que ter memória suficiente para manter o fluxo como um byte[]objeto . Na minha situação em que usei isso, obtendo um OracleBlob, tive que ir para um byte[], era pequeno o suficiente e, além disso, não havia streaming disponível para mim, de qualquer maneira, então acabei de enviar meus bytes para minha função acima.

Outra opção, usando um fluxo, seria usá-lo com a CopyStreamfunção de Jon Skeet que estava em outro post - isso apenas usa FileStreamo fluxo de entrada e cria o arquivo diretamente dele. Ele não usa File.Create, como ele fez (o que inicialmente parecia problemático para mim, mas depois descobriu que provavelmente era apenas um bug do 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
fonte
1
Não é necessário ligar Closepor causa de #using()
Alex78191
@ Alex78191 Se você está falando inputStream.Close(), olhe novamente - inputStreamé enviado como uma variável. O usingestá no path+filenamefluxo de saída. Se você estava falando fs.Close()no meio using, desculpe, você estava certo sobre isso e eu removi isso.
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.
*/
George
fonte
16
Copiar um fluxo byte a byte (usando ReadByte / WriteByte) será muito mais lento que copiar buffer a buffer (usando Read (byte [], int, int) / Write (byte [], int, int)).
Kevin
6

Por que não usar um 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
fonte
46
e se o fluxo de entrada é de 1GB longa - este código seria tentar alocar buffer de 1 GB :)
Buthrakaur
1
Isso não está funcionando com o ResponseStream, porque é de tamanho desconhecido.
Tomas Kubes
Embora seja verdade que você teria que ter a memória disponível para o byte[], acho que seria raro que você estivesse transmitindo um blob de 1 GB + para um arquivo ... a menos que você tenha um site que mantenha torrents em DVD ... , a maioria dos computadores tem pelo menos 2 GB de RAM disponível atualmente, de qualquer maneira ... A ressalva é válida, mas acho que esse é um caso em que provavelmente é "bom o suficiente" para a maioria dos trabalhos.
vapcguy
Os servidores da Web não toleram muito bem um caso como esse, a menos que o site tenha apenas um usuário ativo ao mesmo tempo.
NateTheGreatt
6

Outra opção é obter o fluxo para um byte[]e usar File.WriteAllBytes. Isso deve fazer:

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

O agrupamento em um método de extensão fornece uma melhor nomeação:

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
fonte
3
Se a entrada for muito grande, você receberá uma exceção de falta de memória. A opção de copiar o conteúdo do fluxo de entrada a um filestream é muito melhor
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
fonte
Fornecendo um fluxo de entrada em buffer diretamente para o FileStream- bom!
vapcguy
3

Aqui está um exemplo que usa usos adequados e implementação 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());
            }
        }
    }
}

... e há também isso

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

A chave é entender o uso adequado do uso (que deve ser implementado na instanciação do objeto que implementa o idisposable, como mostrado acima), e ter uma boa idéia de como as propriedades funcionam para fluxos. Posição é literalmente o índice dentro do fluxo (que começa em 0) que é seguido à medida que cada byte é lido usando o método readbyte. Neste caso, eu estou essencialmente usando-o no lugar de uma variável de loop for e simplesmente deixando-o seguir até o comprimento que é literalmente o fim de todo o fluxo (em bytes). Ignore em bytes, porque é praticamente o mesmo e você terá algo simples e elegante como esse que resolve tudo de forma limpa.

Lembre-se também de que o método ReadByte simplesmente converte o byte para um int no processo e pode simplesmente ser convertido novamente.

Vou adicionar outra implementação que escrevi recentemente para criar um tipo de buffer dinâmico para garantir gravações seqüenciais de dados para evitar sobrecarga maciça

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

A explicação é bastante simples: sabemos que precisamos ter em mente todo o conjunto de dados que desejamos escrever e também que queremos apenas escrever certas quantidades; portanto, queremos que o primeiro loop com o último parâmetro esteja vazio (o mesmo que enquanto ) Em seguida, inicializamos um buffer de matriz de bytes definido como o tamanho do que é passado e, com o segundo loop, comparamos j com o tamanho do buffer e o tamanho do original, e se for maior que o tamanho do original matriz de bytes, finalize a execução.

Andrew Ramshaw
fonte