2008-10-11 14 views
14

Acabo de jugar con la API del sistema de archivos Java, y obtuve la siguiente función, que se usa para copiar archivos binarios. La fuente original provino de la Web, pero agregué cláusulas try/catch/finally para asegurarme de que, en caso de que ocurriera algo mal, se cerraran los Buffer Streams (y, por lo tanto, mis recursos de SO liberados) antes de abandonar la función.RAII en Java ... ¿la eliminación de recursos siempre es tan desagradable?

I ajustado abajo la función de mostrar el patrón:

public static void copyFile(FileOutputStream oDStream, FileInputStream oSStream) throw etc... 
{ 
    BufferedInputStream oSBuffer = new BufferedInputStream(oSStream, 4096); 
    BufferedOutputStream oDBuffer = new BufferedOutputStream(oDStream, 4096); 

    try 
    { 
     try 
     { 
     int c; 

     while((c = oSBuffer.read()) != -1) // could throw a IOException 
     { 
      oDBuffer.write(c); // could throw a IOException 
     } 
     } 
     finally 
     { 
     oDBuffer.close(); // could throw a IOException 
     } 
    } 
    finally 
    { 
     oSBuffer.close(); // could throw a IOException 
    } 
} 

Por lo que yo entiendo, no puedo poner los dos close() en la cláusula finalmente, porque la primera close() bien podría lanzar, y luego, la segundo no se ejecutará.

Sé que C# tiene el Eliminar el patrón que hubiera manejado esto con la palabra clave using.

siquiera sé mejor un código C++ habría sido algo así como (usando una API similar a Java):

void copyFile(FileOutputStream & oDStream, FileInputStream & oSStream) 
{ 
    BufferedInputStream oSBuffer(oSStream, 4096); 
    BufferedOutputStream oDBuffer(oDStream, 4096); 

    int c; 

    while((c = oSBuffer.read()) != -1) // could throw a IOException 
    { 
     oDBuffer.write(c); // could throw a IOException 
    } 

    // I don't care about resources, as RAII handle them for me 
} 

me falta algo, o hacer que realmente tiene que producir código feo e hinchado en Java solo para manejar excepciones en el método close() de un flujo en búfer?

(Por favor, dime que estoy equivocado en alguna parte ...)

EDIT: ¿Soy yo, o cuando la actualización de esta página, vi la pregunta y todas las respuestas se redujo en un punto de una ¿un par de minutos? ¿Alguien se divierte demasiado mientras se mantiene en el anonimato?

EDIT 2: McDowell le mostrará un enlace muy interesante me sentí que tenía que mencionar aquí: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make-mess-of-stream.html

Datos 3: siguiente enlace de McDowell, que cayeron sobre una propuesta para Java 7 de un patrón similar al de el patrón de uso de C#: http://tech.puredanger.com/java7/#resourceblock. Mi problema está explícitamente descrito. Aparentemente, incluso con Java 7 do, los problemas persisten.

+0

Disculpas por mi respuesta equivocada. No estaba seguro de si realmente te gustaba descifrar una forma de hacer RAII en Java, o simplemente no sabía cómo uno normalmente copia los datos de un flujo de entrada en un flujo de salida en Java. – Alexander

+0

No hay problema ... El hecho es que tampoco sabía cómo hacer una copia limpia y eficiente ... :-p ... – paercebal

+0

Puede guardar 8 líneas de código en el primer listado y 2 líneas en el segundo listando si usarás la convención del código java. En ese caso, este código no será tan feo. – msangel

Respuesta

16

El patrón try/finally es la forma correcta de manejar flujos en la mayoría de los casos para Java 6 y versiones anteriores.

Algunos defienden el cierre silencioso de las transmisiones. Tenga cuidado de hacer esto por las siguientes razones: Java: how not to make a mess of stream handling


Java 7 introduce try-con-recursos:

/** transcodes text file from one encoding to another */ 
public static void transcode(File source, Charset srcEncoding, 
          File target, Charset tgtEncoding) 
                  throws IOException { 
    try (InputStream in = new FileInputStream(source); 
     Reader reader = new InputStreamReader(in, srcEncoding); 
     OutputStream out = new FileOutputStream(target); 
     Writer writer = new OutputStreamWriter(out, tgtEncoding)) { 
     char[] buffer = new char[1024]; 
     int r; 
     while ((r = reader.read(buffer)) != -1) { 
      writer.write(buffer, 0, r); 
     } 
    } 
} 

AutoCloseable tipos se cerrarán automáticamente:

public class Foo { 
    public static void main(String[] args) { 
    class CloseTest implements AutoCloseable { 
     public void close() { 
     System.out.println("Close"); 
     } 
    } 
    try (CloseTest closeable = new CloseTest()) {} 
    } 
} 
+0

Interesante enlace. Gracias. +1. – paercebal

+0

En la mayoría de los casos, pero curiosamente no en este caso. :) –

+0

@Tom - sí, este no es un buen mecanismo de copia de secuencias + Me gustaría ir con las suyas. – McDowell

3

Desafortunadamente, este tipo de código tiende a hincharse un poco en Java.

Por cierto, si una de las llamadas a oSBuffer.read o oDBuffer.write arroja una excepción, entonces es probable que desee permitir que esa excepción penetre en la jerarquía de llamadas.

Tener una llamada desprotegida para cerrar() dentro de una cláusula finally hará que la excepción original sea reemplazada por una producida por la llamada close(). En otras palabras, un método close() fallido puede ocultar la excepción original producida por read() o write(). Por lo tanto, creo que desea ignorar las excepciones lanzadas por close() si y solo si los otros métodos no arrojaron.

lo general resolver esto incluyendo un primer llamado explícito, dentro del intento interno:

 
    try { 
    while (...) { 
     read... 
     write... 
    } 
    oSBuffer.close(); // exception NOT ignored here 
    oDBuffer.close(); // exception NOT ignored here 
    } finally { 
    silentClose(oSBuffer); // exception ignored here 
    silentClose(oDBuffer); // exception ignored here 
    } 
 
    static void silentClose(Closeable c) { 
    try { 
     c.close(); 
    } catch (IOException ie) { 
     // Ignored; caller must have this intention 
    } 
    } 

Por último, para el funcionamiento, el código debe probablemente trabajar con tampones (múltiples bytes por lectura/escritura) No puede respaldar eso por números, pero menos llamadas deberían ser más eficientes que agregar secuencias almacenadas en búfer en la parte superior.

+0

Si cierras silenciosamente de esta manera, su código no maneja errores, si close arroja una excepción. Muchas secuencias (como BufferedOutputStream) escriben datos al cerrar. – McDowell

+0

McDowell: Sí, debería detectar excepciones si close() arroja una excepción. Tenga en cuenta que las llamadas close() - se realizan DENTRO del try-block. El bloque final es para asegurar la limpieza si algún método arroja una excepción. ¿Derecha? (Tenga en cuenta que olvidé el cierre de uno de los buffers en la primera versión de la publicación.) – volley

+0

BufferedOutputStream es un poco un error. Estoy a favor de un color explícito (en el caso de no excepción), pero tienes que recordarlo. IIRC, el cierre había roto la entrega de excepciones antes de Java SE 1.6. –

4

Hay son problemas, pero el código que encontraste en la web es realmente pobre.

Al cerrar las secuencias de búfer se cierra la secuencia de abajo. Realmente no quieres hacer eso. Todo lo que quiere hacer es purgar el flujo de salida. Además, no tiene sentido especificar las secuencias subyacentes para los archivos. El rendimiento es malo porque está copiando un byte a la vez (en realidad, si utiliza el uso de java.io, puede usar transferTo/transferFrom, que es un poco más rápido). Mientras estamos a punto, los nombres de las variables apestan. Por lo tanto:

public static void copy(
    InputStream in, OutputStream out 
) throw IOException { 
    byte[] buff = new byte[8192]; 
    for (;;) { 
     int len = in.read(buff); 
     if (len == -1) { 
      break; 
     } 
     out.write(buff, 0, len); 
    } 
} 

Si usted se encuentra utilizando try-finally mucho, entonces se puede factorizar a cabo con el "ejecutar alrededor de" expresión idiomática.

En mi opinión: Java debería tener alguna manera de cerrar los recursos al final del alcance. Sugiero que se agregue private como un operador de postes unarios para cerrar al final del bloque adjunto.

+0

Gracias por el mejor código. Para mi proyecto personal actual, no es muy importante, pero copio/pego su código en este momento como un reemplazo futuro. +1. – paercebal

+0

Si copia el archivo, probablemente * quiera * cerrar las transmisiones cuando haya terminado. La copia está completa, por lo que no tiene sentido dejar las secuencias abiertas. En este caso, sus llamadas anidadas try-finally blocks + close() son apropiadas. –

+0

Eso debe hacerse mediante el método que abre las secuencias de archivos. –

3

Sí, así es como funciona Java. Hay inversión de control: el usuario del objeto debe saber cómo limpiar el objeto en lugar de limpiarlo por sí mismo. Esto desafortunadamente lleva a una gran cantidad de código de limpieza dispersos a través de su código java.

C# tiene la palabra clave "using" para llamar automáticamente a Dispose cuando un objeto se sale del alcance. Java no tiene tal cosa.

+1

Si hay una sintaxis especial para él o no, el código del cliente tiene que decirle al recurso cuándo realizar la limpieza. No hay forma de que el recurso lo sepa. Por supuesto, puede abstraer la adquisición de recursos, configurar y liberar, con el código interesante ejecutado como devolución de llamada. –

+0

Eso no es verdad. Los objetos C++ y C# simplemente se deshacen de ellos mismos sin la participación de la persona que llama. Lea sobre estos idiomas. –

+1

Tengo cierta familiaridad con esos idiomas. En C#, el código del cliente llama al método de eliminación al final del bloque de uso. En C++, el código del cliente llama al destructor al final del alcance. –

2

Para tareas comunes de IO, como copiar un archivo, un código como el que se muestra arriba está reinventando la rueda. Desafortunadamente, el JDK no proporciona servicios de nivel superior, pero apache commons -io sí.

Por ejemplo, FileUtils contiene varios métodos de utilidad para trabajar con archivos y directorios (incluida la copia). Por otro lado, si realmente necesita usar la compatibilidad con IO en el JDK, IOUtils contiene un conjunto de métodos closeQuietly() que cierran Readers, Writers, Streams, etc. sin tirar excepciones.

Cuestiones relacionadas