Manera fácil de escribir contenidos de un Java InputStream en un OutputStream


445

Me sorprendió encontrar hoy que no podía rastrear ninguna forma simple de escribir el contenido de una InputStreama una OutputStreamen Java. Obviamente, el código de búfer de bytes no es difícil de escribir, pero sospecho que me falta algo que me facilitaría la vida (y el código más claro).

Entonces, dada una InputStream iny una OutputStream out, ¿hay una manera más simple de escribir lo siguiente?

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

Usted mencionó en un comentario que esto es para una aplicación móvil. ¿Es Android nativo? Si es así, avíseme y publicaré otra respuesta (se puede hacer con una sola línea de código en Android).
Jabari

Respuestas:


182

Java 9

Desde Java 9, InputStreamproporciona un método llamado transferTocon la siguiente firma:

public long transferTo(OutputStream out) throws IOException

Como lo indica la documentacióntransferTo :

Lee todos los bytes de esta secuencia de entrada y los escribe en la secuencia de salida dada en el orden en que se leen. Al regresar, esta secuencia de entrada estará al final de la secuencia. Este método no cierra ninguna secuencia.

Este método puede bloquear la lectura indefinida de la secuencia de entrada o la escritura en la secuencia de salida. El comportamiento para el caso donde el flujo de entrada y / o salida se cierra asincrónicamente, o el hilo se interrumpe durante la transferencia, es altamente específico de flujo de entrada y salida, y por lo tanto no se especifica

Entonces, para escribir el contenido de un Java InputStreamen un OutputStream, puede escribir:

input.transferTo(output);

11
Debes preferir Files.copytanto como sea posible. Se implementa en código nativo y, por lo tanto, puede ser más rápido. transferTodebe usarse solo si ambas secuencias no son FileInputStream / FileOutputStream.
ZhekaKozlov

@ZhekaKozlov Lamentablemente Files.copyno maneja ninguna secuencia de entrada / salida, pero está específicamente diseñado para secuencias de archivos .
El Empalador

396

Como mencionó WMR, org.apache.commons.io.IOUtilsdesde Apache tiene un método llamado copy(InputStream,OutputStream)que hace exactamente lo que estás buscando.

Así que tienes:

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

... en tu código.

¿Hay alguna razón por la que estás evitando IOUtils ?


170
Lo estoy evitando para esta aplicación móvil que estoy construyendo porque quintuplicará el tamaño de la aplicación para guardar 5 líneas de código.
Jeremy Logan

36
quizás valga la pena mencionar eso iny outdebe cerrarse al final del código en un bloque finalmente
basZero

24
@basZero O usando un bloque try con recursos.
Warren Dew

1
O bien, podría escribir su propio contenedor de copia (dentro, fuera) ... (en menos tiempo para ...)
MikeM

1
Si ya está utilizando la biblioteca Guava, Andrejs ha recomendado la clase ByteStreams a continuación. Similar a lo que hace IOUtils, pero evita agregar Commons IO a su proyecto.
Jim Tough

328

Si está utilizando Java 7, los archivos (en la biblioteca estándar) son el mejor enfoque:

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

Editar: Por supuesto, es útil cuando crea uno de InputStream o OutputStream desde el archivo. Use file.toPath()para obtener la ruta del archivo.

Para escribir en un archivo existente (por ejemplo, uno creado con File.createTempFile()), deberá pasar la REPLACE_EXISTINGopción de copia (de lo contrario, FileAlreadyExistsExceptionse arroja):

Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING)

26
No creo que esto realmente resuelva el problema ya que un extremo es un camino. Si bien puede obtener una ruta para un archivo, por lo que sé, no puede obtener una para ninguna secuencia genérica (por ejemplo, una a través de la red).
Matt Sheppard

44
¡CopyOptions es arbitrario! Puedes ponerlo aquí si lo deseas.
user1079877

44
ahora esto es lo que estaba buscando! JDK al rescate, no hay necesidad de otra biblioteca
Don Cheadle

77
FYI, FilesNO está disponible en Java 1.7 de Android . Esto me picó: stackoverflow.com/questions/24869323/…
Joshua Pinter el

23
Divertidamente, el JDK también tiene una Files.copy()que toma dos flujos, y es lo que todas las demás Files.copy()funciones adelantan para hacer el trabajo real de copia. Sin embargo, es privado (ya que en realidad no involucra rutas o archivos en esa etapa), y se ve exactamente como el código en la propia pregunta del OP (más una declaración de devolución). Sin apertura, sin cierre, solo un bucle de copia.
Ti Strga

102

Creo que esto funcionará, pero asegúrese de probarlo ... una "mejora" menor, pero podría ser un poco costoso en la legibilidad.

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

26
Sugiero un búfer de al menos 10 KB a 100 KB. Eso no es mucho y puede acelerar enormemente la copia de grandes cantidades de datos.
Aaron Digulla

66
es posible que desee decir en while(len > 0)lugar de != -1, porque este último también podría devolver 0 cuando se usa el read(byte b[], int off, int len)método-, que arroja una excepción @out.write
phil294

12
@Blauhirn: Eso sería incorrecto, ya que es completamente legal de acuerdo con el InputStreamcontrato de lectura para devolver 0 cualquier cantidad de veces. Y de acuerdo con el OutputStreamcontrato, el método de escritura debe aceptar una longitud de 0 y solo debe arrojar una excepción cuando lensea ​​negativo.
Christoffer Hammarström

1
Puede guardar una línea cambiando el whilea foray poniendo una de las variables en la sección de inicio de for: ej for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);. =)
ɳeuroburɳ

1
@Blauhim read()solo puede devolver cero si proporcionó una longitud de cero, lo que sería un error de programación y una estúpida condición de bucle para siempre. Y write()no , no lanzar una excepción si se proporciona una longitud cero.
Marqués de Lorne

54

Usando guayaba ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);

11
¡No te olvides de cerrar las corrientes después de eso!
WonderCsabo

Esta es la mejor respuesta si ya está usando Guava, que se ha vuelto indispensable para mí.
Hong

1
@Hong Debes usar Files.copytanto como sea posible. Use ByteStreams.copysolo si ambas transmisiones no son FileInputStream / FileOutputStream.
ZhekaKozlov

@ZhekaKozlov Gracias por el consejo. En mi caso, la secuencia de entrada es de un recurso de la aplicación de Android (dibujable).
Hong

26

Función simple

Si solo necesita esto para escribir un InputStreama, Fileentonces puede usar esta función simple:

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

44
Gran función, gracias. ¿Sin embargo, necesitarías poner las close()llamadas en finallybloques?
Joshua Pinter el

@JoshPinter No dolería.
Jordan LaPrise

3
Probablemente ambos deberían incluir un bloqueo finalmente y no tragar excepciones en una implementación real. Además, cerrar un InputStream pasado a un método a veces es inesperado por el método de llamada, por lo que uno debe considerar si es el comportamiento que desean.
Cel Skeggs

2
¿Por qué atrapar la excepción cuando es suficiente IOException?
Prabhakar

18

Los JDKutiliza el mismo código por lo que parece que no hay manera "fácil", sin bibliotecas torpe de terceros (que probablemente no hacer nada diferente de todos modos). Lo siguiente se copia directamente 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;
}

2
Sí. Es una pena que esta llamada en particular sea privada y no haya otra opción que copiarla en su propia clase de utilidades, ya que es posible que no esté tratando con archivos, sino con 2 sockets a la vez.
Dragas

17

PipedInputStreamy PipedOutputStreamsolo debe usarse cuando tiene varios subprocesos, como lo señala el Javadoc .

Además, tenga en cuenta que las secuencias de entrada y las secuencias de salida no envuelven las interrupciones de subprocesos con IOExceptions ... Por lo tanto, debe considerar incorporar una política de interrupción a su 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();
    }
}

Esta sería una adición útil si espera utilizar esta API para copiar grandes volúmenes de datos o datos de secuencias que se atascan durante un tiempo intolerablemente largo.


14

Para aquellos que usan Spring Framework, hay una clase útil de StreamUtils :

StreamUtils.copy(in, out);

Lo anterior no cierra las transmisiones. Si desea que las secuencias se cierren después de la copia, use la clase FileCopyUtils en su lugar:

FileCopyUtils.copy(in, out);

8

No hay manera de hacer esto mucho más fácil con los métodos JDK, pero como Apocalisp ya lo ha notado, no eres el único con esta idea: podrías usar IOUtils de Jakarta Commons IO , también tiene muchas otras cosas útiles, que la OMI debería ser parte del JDK ...


6

Usando Java7 y prueba con recursos , viene con una versión simplificada y legible.

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

3
La descarga dentro del circuito es altamente contraproducente.
Marqués de Lorne

5

Aquí viene cómo lo estoy haciendo con el bucle más simple.

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

4

Use la clase Util de Commons Net:

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

3

Un fragmento de IMHO más mínimo (que también abarca de forma más estrecha la variable de longitud):

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

Como nota al margen, no entiendo por qué más personas no utilizan un forbucle, sino que optan por una whileexpresión de asignación y prueba que algunos consideran un estilo "pobre".


1
Su sugerencia provoca una escritura de 0 bytes en la primera iteración. Quizás lo menos que haga:for(int n = 0; (n = in.read(buffer)) > 0;) { out.write(buffer, 0, n); }
Brian de Alwis

2
@BriandeAlwis Tiene razón acerca de que la primera iteración es incorrecta. El código ha sido arreglado (en mi humilde opinión de una manera más limpia que su sugerencia) - vea el código editado. Gracias por cuidar.
Bohemio

3

Esta es mi mejor oportunidad !!

Y no lo use inputStream.transferTo(...)porque es demasiado genérico. El rendimiento de su código será mejor si controla su memoria de búfer.

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

Lo uso con este método (mejorable) cuando sé de antemano el tamaño de la transmisión.

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

2

Creo que es mejor usar un búfer grande, porque la mayoría de los archivos tienen más de 1024 bytes. También es una buena práctica verificar que el número de bytes leídos sea positivo.

byte[] buffer = new byte[4096];
int n;
while ((n = in.read(buffer)) > 0) {
    out.write(buffer, 0, n);
}
out.close();

44
El uso de un búfer grande es una buena idea, pero no porque los archivos sean mayormente> 1k, es para amortizar el costo de las llamadas al sistema.
Marqués de Lorne

1

Yo uso BufferedInputStreamy BufferedOutputStreampara eliminar la semántica del búfer del código

try (OutputStream out = new BufferedOutputStream(...);
     InputStream in   = new BufferedInputStream(...))) {
  int ch;
  while ((ch = in.read()) != -1) {
    out.write(ch);
  }
}

¿Por qué es una buena idea 'eliminar la semántica del búfer del código'?
Marqués de Lorne

2
Significa que no escribo la lógica de almacenamiento en búfer, uso la que está integrada en el JDK, que generalmente es lo suficientemente buena.
Arquímedes Trajano

0

PipedInputStream y PipedOutputStream pueden ser de alguna utilidad, ya que puede conectar uno con el otro.


1
Esto no es bueno para el código de un solo subproceso, ya que podría llegar a un punto muerto; vea esta pregunta stackoverflow.com/questions/484119/…
Raekye

2
Puede ser de alguna utilidad, ¿cómo? Él ya tiene una secuencia de entrada y una secuencia de salida. ¿Cómo agregará otro de cada ayuda exactamente?
Marqués de Lorne

0

Otro posible candidato son las utilidades de E / S de guayaba:

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

Pensé en usarlos, ya que Guava ya es inmensamente útil en mi proyecto, en lugar de agregar otra biblioteca para una función.


Hay copyy toByteArraymétodos en docs.guava-libraries.googlecode.com/git-history/release/javadoc/… (guava llama a las secuencias de entrada / salida como "secuencias de bytes" y lectores / escritores como "secuencias de caracteres")
Raekye

si ya usa las bibliotecas de guayaba, es una buena idea, pero si no, son una biblioteca gigantesca con miles de métodos 'google-way-of-doing-everything-different-to-the-standard'. Me mantendría alejado de ellos
rompe el

"mamut"? 2.7MB con un conjunto muy pequeño de dependencias y una API que evita cuidadosamente duplicar el JDK central.
Adrian Baker el

0

No es muy legible, pero efectivo, no tiene dependencias y se ejecuta con cualquier versión de Java

byte[] buffer = new byte[1024];
for (int n; (n = inputStream.read(buffer)) != -1; outputStream.write(buffer, 0, n));

!= -1o > 0? Esos predicados no son exactamente lo mismo.
El Empalador

! = -1 significa no fin de archivo. Esto no es una iteración sino un while-do-loop disfrazado: 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;
}

44
¿Puede explicar por qué esta es la respuesta correcta?
rfornal


-6

puedes usar este 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){}
 }

66
catch(Exception ex){}- esto es de primera categoría
ᄂ ᄀ
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.