Maneira fácil de gravar o conteúdo de um InputStream Java em um OutputStream

445

Fiquei surpreso ao descobrir hoje que não consegui rastrear nenhuma maneira simples de escrever o conteúdo de um InputStreampara um OutputStreamem Java. Obviamente, o código do buffer de bytes não é difícil de escrever, mas suspeito que estou perdendo algo que tornaria minha vida mais fácil (e o código mais claro).

Então, dado um InputStream ine um OutputStream out, existe uma maneira mais simples de escrever o seguinte?

byte[] buffer = new byte[1024];
int len = in.read(buffer);
while (len != -1) {
    out.write(buffer, 0, len);
    len = in.read(buffer);
}
Matt Sheppard
fonte
Você mencionou em um comentário que isso é para um aplicativo móvel. É Android nativo? Nesse caso, avise-me e postarei outra resposta (isso pode ser feito é uma única linha de código no Android).
Jabari 29/01

Respostas:

182

Java 9

Desde o Java 9, InputStreamfornece um método chamado transferTocom a seguinte assinatura:

public long transferTo(OutputStream out) throws IOException

Como declara a documentação , transferToirá:

Lê todos os bytes desse fluxo de entrada e grava os bytes no fluxo de saída especificado na ordem em que são lidos. No retorno, esse fluxo de entrada estará no final do fluxo. Este método não fecha nenhum fluxo.

Este método pode bloquear a leitura indefinida do fluxo de entrada ou a gravação no fluxo de saída. O comportamento do caso em que o fluxo de entrada e / ou saída é fechado de forma assíncrona ou o encadeamento interrompido durante a transferência é altamente específico do fluxo de entrada e saída e, portanto, não é especificado

Portanto, para escrever o conteúdo de um Java InputStreamem um OutputStream, você pode escrever:

input.transferTo(output);
Ali Dehghani
fonte
11
Você deve preferir Files.copyo máximo possível. É implementado em código nativo e, portanto, pode ser mais rápido. transferTodeve ser usado apenas se os dois fluxos não forem FileInputStream / FileOutputStream.
ZhekaKozlov
@ZhekaKozlov Infelizmente Files.copy, não lida com nenhum fluxo de entrada / saída, mas foi projetado especificamente para fluxos de arquivos .
The Empaler
396

Como o WMR mencionou, o org.apache.commons.io.IOUtilsApache possui um método chamado copy(InputStream,OutputStream)que faz exatamente o que você está procurando.

Então você tem:

InputStream in;
OutputStream out;
IOUtils.copy(in,out);
in.close();
out.close();

... no seu código.

Existe uma razão para você estar evitando IOUtils?

Mikezx6r
fonte
170
Estou evitando esse aplicativo para dispositivos móveis que estou criando, porque ele quintuplicava o tamanho do aplicativo, economizando apenas 5 linhas de código.
Jeremy Logan
36
é talvez vale a pena mencionar que ine outdeve ser fechado no final do código em um bloco finally
basZero
24
@basZero Ou usando uma tentativa com bloco de recursos.
Warren Dew
1
Ou você poderia apenas escrever sua própria cópia (entrada, saída) invólucro ... (em menos tempo para fazer para ...)
MikeM
1
Se você já estiver usando a biblioteca Guava, Andrejs recomendou a classe ByteStreams abaixo. Semelhante ao que o IOUtils faz, mas evita adicionar o Commons IO ao seu projeto.
Jim resistente
328

Se você estiver usando o Java 7, o Files (na biblioteca padrão) é a melhor abordagem:

/* You can get Path from file also: file.toPath() */
Files.copy(InputStream in, Path target)
Files.copy(Path source, OutputStream out)

Editar: claro que é apenas útil quando você cria um de InputStream ou OutputStream a partir do arquivo. Usarfile.toPath() para obter o caminho do arquivo.

Para gravar em um arquivo existente (por exemplo, um criado com File.createTempFile()), você precisará passar a REPLACE_EXISTINGopção de cópia (caso contrário, FileAlreadyExistsExceptionserá lançada):

Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING)
user1079877
fonte
26
Eu não acho que isso realmente resolva o problema, já que um fim é um caminho. Embora você possa obter um caminho para um arquivo, pelo que sei, não é possível obter um para nenhum fluxo genérico (por exemplo, um pela rede).
precisa
4
CopyOptions é arbitrário! Você pode colocá-lo aqui, se quiser.
User1079877
4
agora é isso que eu estava procurando! JDK para o resgate, não há necessidade de outra biblioteca
Don Cheadle
7
Para sua informação, FilesNÃO está disponível no Java 1.7 do Android . I got Picado por este: stackoverflow.com/questions/24869323/...
Joshua Pinter
23
Divertidamente, o JDK também possui um Files.copy()que recebe dois fluxos e é o que todas as outras Files.copy()funções encaminham para realizar o trabalho real de cópia. No entanto, é privado (já que na verdade não envolve Caminhos ou Arquivos nesse estágio) e se parece exatamente com o código na pergunta do OP (além de uma declaração de retorno). Sem abertura, sem fechamento, apenas um loop de cópia.
Ti Strga 9/09/15
102

Eu acho que isso funcionará, mas certifique-se de testá-lo ... "pequenas melhorias", mas pode ser um pouco de custo na legibilidade.

byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
    out.write(buffer, 0, len);
}
Mike Stone
fonte
26
Sugiro um buffer de pelo menos 10 KB a 100 KB. Isso não é muito e pode acelerar a cópia de grandes quantidades de dados tremendamente.
Aaron Digulla
6
você pode querer dizer while(len > 0), em vez de != -1, porque este último também pode retornar 0 ao usar o read(byte b[], int off, int len)-method, que lança uma exceção @out.write
phil294
12
@Blauhirn: Isso seria incorreto, pois é totalmente legal, de acordo com o InputStreamcontrato, que a leitura retorne 0 várias vezes. E de acordo com o OutputStreamcontrato, o método de gravação deve aceitar um comprimento de 0 e só deve lançar uma exceção quando lenfor negativo.
Christoffer Hammarström
1
Você pode salvar uma linha alterando o whilepara a fore colocando uma das variáveis ​​na seção init do for: por exemplo for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);,. =)
ɲeuroburɳ
1
O @Blauhim read()só pode retornar zero se você tiver fornecido um comprimento igual a zero, o que seria um erro de programação e uma condição estúpida para repetir para sempre. E write()se não lançar uma exceção se você fornecer um comprimento zero.
Marquês de Lorne
54

Usando goiabas ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);
Andrejs
fonte
11
Não se esqueça de fechar os fluxos depois disso!
WonderCsabo #
Esta é a melhor resposta se você já estiver usando o Goiaba, que se tornou indispensável para mim.
Hong
1
@ Hong Você deve usar Files.copyo máximo possível. Use ByteStreams.copyapenas se os dois fluxos não forem FileInputStream / FileOutputStream.
ZhekaKozlov
@ZhekaKozlov Obrigado pela dica. No meu caso, o fluxo de entrada é do recurso de um aplicativo Android (extraível).
Hong
26

Função Simples

Se você só precisa disso para escrever um InputStreampara um File, pode usar esta função simples:

private void copyInputStreamToFile( InputStream in, File file ) {
    try {
        OutputStream out = new FileOutputStream(file);
        byte[] buf = new byte[1024];
        int len;
        while((len=in.read(buf))>0){
            out.write(buf,0,len);
        }
        out.close();
        in.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
Jordan LaPrise
fonte
4
Ótima função, obrigado. Você precisaria colocar as close()chamadas em finallyblocos?
Joshua Pinter
@ JoshPinter Não faria mal.
Jordan LaPrise
3
Você provavelmente deve incluir um bloco final e não engolir exceções em uma implementação real. Além disso, o fechamento de um InputStream passado para um método às vezes é inesperado pelo método de chamada, portanto, deve-se considerar se é o comportamento que eles desejam.
Cel Skeggs
2
Por que capturar a exceção quando IOException é suficiente?
Prabhakar
18

Ele JDKusa o mesmo código, então parece que não há uma maneira "mais fácil" sem bibliotecas desajeitadas de terceiros (que provavelmente não fazem nada diferente de qualquer maneira). O seguinte é copiado diretamente de java.nio.file.Files.java:

// buffer size used for reading and writing
private static final int BUFFER_SIZE = 8192;

/**
  * Reads all bytes from an input stream and writes them to an output stream.
  */
private static long copy(InputStream source, OutputStream sink) throws IOException {
    long nread = 0L;
    byte[] buf = new byte[BUFFER_SIZE];
    int n;
    while ((n = source.read(buf)) > 0) {
        sink.write(buf, 0, n);
        nread += n;
    }
    return nread;
}
BullyWiiPlaza
fonte
2
Sim. É uma pena que essa chamada em particular seja privada e não há outra opção senão copiá-la para sua própria classe de utilitários, pois é possível que você não esteja lidando com arquivos, mas com dois soquetes de uma só vez.
Dragas
17

PipedInputStreame PipedOutputStreamsó deve ser usado quando você possui vários encadeamentos, conforme observado pelo Javadoc .

Além disso, observe que os fluxos de entrada e de saída não envolvem nenhuma interrupção de encadeamento com IOExceptions ... Portanto, considere incorporar uma política de interrupção ao seu código:

byte[] buffer = new byte[1024];
int len = in.read(buffer);
while (len != -1) {
    out.write(buffer, 0, len);
    len = in.read(buffer);
    if (Thread.interrupted()) {
        throw new InterruptedException();
    }
}

Isso seria uma adição útil se você espera usar essa API para copiar grandes volumes de dados ou dados de fluxos que ficam presos por um tempo intoleravelmente longo.

Dilum Ranatunga
fonte
14

Para quem usa o framework Spring, existe uma classe StreamUtils útil :

StreamUtils.copy(in, out);

O acima não fecha os fluxos. Se você deseja que os fluxos sejam fechados após a cópia, use a classe FileCopyUtils :

FileCopyUtils.copy(in, out);
holmis83
fonte
8

Não há como fazer isso muito mais facilmente com os métodos JDK, mas como o Apocalisp já observou, você não é o único com essa ideia: você pode usar IOUtils do Jakarta Commons IO , mas também tem muitas outras coisas úteis, que a IMO deveria realmente fazer parte do JDK ...

WMR
fonte
6

Usando Java7 e try-with-resources , vem com uma versão simplificada e legível.

try(InputStream inputStream = new FileInputStream("C:\\mov.mp4");
    OutputStream outputStream = new FileOutputStream("D:\\mov.mp4")) {

    byte[] buffer = new byte[10*1024];

    for (int length; (length = inputStream.read(buffer)) != -1; ) {
        outputStream.write(buffer, 0, length);
    }
} catch (FileNotFoundException exception) {
    exception.printStackTrace();
} catch (IOException ioException) {
    ioException.printStackTrace();
}
Sivakumar
fonte
3
A descarga dentro do loop é altamente contraproducente.
Marquês de Lorne
5

Aí vem como estou indo com o loop for mais simples.

private void copy(final InputStream in, final OutputStream out)
    throws IOException {
    final byte[] b = new byte[8192];
    for (int r; (r = in.read(b)) != -1;) {
        out.write(b, 0, r);
    }
}
Jin Kwon
fonte
4

Use a classe Util da Commons Net:

import org.apache.commons.net.io.Util;
...
Util.copyStream(in, out);
DejanLekic
fonte
3

Um trecho mais mínimo da IMHO (que também escopo mais estreitamente a variável length):

byte[] buffer = new byte[2048];
for (int n = in.read(buffer); n >= 0; n = in.read(buffer))
    out.write(buffer, 0, n);

Como uma observação lateral, não entendo por que mais pessoas não usam um forloop, em vez disso, optam por uma whilecom uma expressão de atribuição e teste que é considerada por alguns como estilo "ruim".

Boêmio
fonte
1
Sua sugestão causa uma gravação de 0 byte na primeira iteração. Talvez menos:for(int n = 0; (n = in.read(buffer)) > 0;) { out.write(buffer, 0, n); }
Brian de Alwis
2
@BriandeAlwis Você está certo sobre a primeira iteração estar incorreta. O código foi corrigido (IMHO de uma maneira mais limpa que a sua sugestão) - veja o código editado. Thx por cuidar.
Bohemian
3

Esta é a minha melhor chance !!

E não use inputStream.transferTo(...)porque é muito genérico. O desempenho do seu código será melhor se você controlar sua memória buffer.

public static void transfer(InputStream in, OutputStream out, int buffer) throws IOException {
    byte[] read = new byte[buffer]; // Your buffer size.
    while (0 < (buffer = in.read(read)))
        out.write(read, 0, buffer);
}

Eu o uso com esse método (improvável) quando conheço antecipadamente o tamanho do fluxo.

public static void transfer(int size, InputStream in, OutputStream out) throws IOException {
    transfer(in, out,
            size > 0xFFFF ? 0xFFFF // 16bits 65,536
                    : size > 0xFFF ? 0xFFF// 12bits 4096
                            : size < 0xFF ? 0xFF // 8bits 256
                                    : size
    );
}
Daniel De León
fonte
2

Eu acho que é melhor usar um buffer grande, porque a maioria dos arquivos tem mais de 1024 bytes. Também é uma boa prática verificar se o número de bytes de leitura é positivo.

byte[] buffer = new byte[4096];
int n;
while ((n = in.read(buffer)) > 0) {
    out.write(buffer, 0, n);
}
out.close();
Alexander Volkov
fonte
4
Usar um buffer grande é, de fato, uma boa idéia, mas não porque os arquivos tenham mais de 1k, é para amortizar o custo das chamadas do sistema.
Marquês de Lorne
1

Eu uso BufferedInputStreame BufferedOutputStreampara remover a semântica de buffer do código

try (OutputStream out = new BufferedOutputStream(...);
     InputStream in   = new BufferedInputStream(...))) {
  int ch;
  while ((ch = in.read()) != -1) {
    out.write(ch);
  }
}
Archimedes Trajano
fonte
Por que 'remover a semântica de buffer do código' é uma boa idéia?
Marquês de Lorne
2
Isso significa que eu não escrevo a lógica de buffer, eu uso a que está embutida no JDK, que geralmente é boa o suficiente.
Archimedes Trajano
0

PipedInputStream e PipedOutputStream podem ser de alguma utilidade, pois você pode conectar um ao outro.

Arktronic
fonte
1
Isso não é bom para o código de thread único, pois pode causar um conflito; veja esta pergunta stackoverflow.com/questions/484119/…
Raekye 6/13
2
Pode ser de alguma utilidade como? Ele já tem um fluxo de entrada e um fluxo de saída. Como adicionar exatamente outro de cada ajuda exatamente?
Marquês de Lorne
0

Outro candidato possível são os utilitários de E / S do Guava:

http://code.google.com/p/guava-libraries/wiki/IOExplained

Eu pensei em usá-los, pois o Guava já é imensamente útil no meu projeto, em vez de adicionar mais uma biblioteca para uma função.

Andrew Mao
fonte
Existem copye toByteArraymétodos em docs.guava-libraries.googlecode.com/git-history/release/javadoc/… (a goiaba chama fluxos de entrada / saída como "fluxos de bytes" e leitores /
gravadores
se você já usa bibliotecas de goiaba, é uma boa idéia, mas se não, elas são uma biblioteca gigantesca com milhares de métodos 'google-maneira-de-fazer-tudo-diferente-do-padrão'. Eu ficaria longe deles
rompe 28/11/14
"mamute"? 2,7 MB com um conjunto muito pequeno de dependências e uma API que evita cuidadosamente a duplicação do JDK principal.
Adrian Baker
0

Não é muito legível, mas eficaz, não possui dependências e é executado com qualquer versão java

byte[] buffer = new byte[1024];
for (int n; (n = inputStream.read(buffer)) != -1; outputStream.write(buffer, 0, n));
IPP Nerd
fonte
!= -1ou > 0? Esses predicados não são exatamente os mesmos.
The Empaler
! = -1 significa não-fim-de-arquivo. Esta não é uma iteração, mas um loop while-do-disfarçado: while ((n = inputStream.read (buffer))! = -1) do {outputStream.write (buffer, 0, n)}
IPP Nerd
-1
public static boolean copyFile(InputStream inputStream, OutputStream out) {
    byte buf[] = new byte[1024];
    int len;
    long startTime=System.currentTimeMillis();

    try {
        while ((len = inputStream.read(buf)) != -1) {
            out.write(buf, 0, len);
        }

        long endTime=System.currentTimeMillis()-startTime;
        Log.v("","Time taken to transfer all bytes is : "+endTime);
        out.close();
        inputStream.close();

    } catch (IOException e) {

        return false;
    }
    return true;
}
Nour Rteil
fonte
4
Você pode explicar por que essa é a resposta certa?
rfornal
-6

você pode usar esse método

public static void copyStream(InputStream is, OutputStream os)
 {
     final int buffer_size=1024;
     try
     {
         byte[] bytes=new byte[buffer_size];
         for(;;)
         {
           int count=is.read(bytes, 0, buffer_size);
           if(count==-1)
               break;
           os.write(bytes, 0, count);
         }
     }
     catch(Exception ex){}
 }
Pranav
fonte
6
catch(Exception ex){}- this is top-notch #
25417