Pregunta

Acabo de jugar con la API del sistema de archivos Java y obtuve la siguiente función, utilizada para copiar archivos binarios. La fuente original provenía de la Web, pero agregué las cláusulas try / catch / finally para asegurarme de que, en caso de que ocurriera algo incorrecto, Buffer Streams se cerraría (y, por lo tanto, se liberarían los recursos de mi sistema operativo) antes de salir de la función.

Recorté la función para 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
   }
}

Hasta donde lo entiendo, no puedo poner los dos close () en la cláusula final porque el primer close () podría arrojar, y luego, el segundo no se ejecutará.

Sé que C # tiene el patrón Dispose que habría manejado esto con la palabra clave using .

Incluso sé mejor que un código C ++ habría sido algo así (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 realmente tengo que producir un código feo e hinchado en Java solo para manejar las excepciones en el método close () de un flujo almacenado en búfer?

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

EDITAR: ¿Soy yo o al actualizar esta página, vi que tanto la pregunta como todas las respuestas disminuyeron en un punto en un par de minutos? ¿Alguien se divierte demasiado mientras permanece anónimo?

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

EDITAR 3: Siguiendo el enlace de McDowell, recurrí a una propuesta para Java 7 de un patrón similar al patrón C # usando: http://tech.puredanger.com/java7/#resourceblock . Mi problema se describe explícitamente. Aparentemente, incluso con Java 7 do , los problemas persisten.

¿Fue útil?

Solución

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 abogan por cerrar silenciosamente las transmisiones. Tenga cuidado al hacer esto por estos motivos: Java: cómo no hacer un lío de manejo de flujo


Java 7 presenta prueba 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 los 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()) {}
  }
}

Otros consejos

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

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

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 te encuentras usando try-finally mucho, entonces puedes factorizarlo con el " ejecutar alrededor de " modismo.

En mi opinión: Java debería tener alguna forma de cerrar los recursos al final del alcance. Sugiero agregar private como un operador de postfix unario para cerrar al final del bloque adjunto.

Sí, así es como funciona Java. Existe una inversión de control: el usuario del objeto debe saber cómo limpiar el objeto en lugar de que el propio objeto se limpie después de sí mismo. Desafortunadamente, esto lleva a una gran cantidad de código de limpieza disperso en todo el código de Java.

C # tiene el " utilizando " palabra clave para llamar automáticamente Desechar cuando un objeto se sale del alcance. Java no tiene tal cosa.

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 dejar que esa excepción permee en la jerarquía de llamadas.

Tener una llamada sin protección 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 fallido close () 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.

Normalmente resuelvo esto al incluir una llamada cerrada explícita, 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
    }
  }

Finalmente, para el rendimiento, el código probablemente debería funcionar con buffers (múltiples bytes por lectura / escritura). No puedo respaldar eso por números, pero menos llamadas deberían ser más eficientes que agregar secuencias almacenadas en la parte superior.

Para tareas comunes de E / S, como copiar un archivo, un código como el que se muestra arriba está reinventando la rueda. Desafortunadamente, el JDK no proporciona ninguna utilidad 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 el soporte de IO en el JDK, IOUtils contiene un conjunto de métodos closeQuietly () que cierran lectores, escritores, secuencias, etc. sin generar excepciones.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top