8 de febrero de 2019

Leer y guardar texto en un fichero

En esta sesión voy a poner sendos ejemplos de lectura y escritura de un texto en disco.

Si buscáis por Internet encontraréis muchas formas distintas de hacerlo usando distintos tipos. Esto es debido a que hay distintas implementaciones de las clases Reader y Writer:
  • Unas permiten especificar el soporte (no tiene por qué usarse un fichero, podríamos leer y escribir de un socket web o un buffer de datos cualquiera)
  • Unas mejoran el rendimiento del ancho del buffer
  • Unas están especializadas en guardar texto y otras para datos en crudo (raw)
  • Algunos tienen métodos particulares que nos pueden resultar útiles y no pertenecen a las interfaces (como newLine())
  • Además hay que sumar las distintas librerías que hay para facilitar el tratamiento de estas operaciones como Apache common o guava.

Por todo lo anterior y desde mi punto de vista, voy a usar la forma más eficiente y flexible para hacer operaciones con texto sobre ficheros:
  1. Usaré sólo Java Nativo.
  2. Usaré BufferedReader/BufferedWriter porque me va a encapsular un Reader/Writer para mejorar el rendimiento en los accesos al disco (en cada acceso va a usar el buffer completo y por tanto necesitará menos operaciones de acceso)
  3. Usaré un InputStreamReader/OutputStreamWriter porque me van a permitir especificar la codificación de mi texto (usaré UTF-8)
  4. Usaré un FileInputStream/FileOutputStream para realizar operaciones sobre archivos.
Este tipo de operaciones van a reservar recursos de mi sistema (si tengo un fichero abierto porque lo estoy usando, otro proceso no podrá usarlo, como cuando queremos sacar nuestro USB y nos dice que está en uso). Por tanto, cuando dejemos de usar un archivo hay que liberarlo para que no se quede reservado impidiendo su uso. Para eso existe el método close().

También estás operaciones suelen declarar varias excepciones (cuando no existe un fichero, no se tiene acceso, hay una codificación desconocida... cualquier error de entrada y salida). Esto hace que deban estar rodeadas de la cláusula try-catch para controlarlas.

No obstante, como BufferedReader/Writer implementan la interfaz AutoCloseable, voy a usar un try-with-resources para que se encargue automáticamente de cerrarme el fichero cuando acabe de usar mi Reader/Writer.

El código para la lectura queda:
public static String leer (String ruta) {
    String leido = "";
    
    try (BufferedReader buffer = new BufferedReader(
                new InputStreamReader(
                        new FileInputStream(ruta), "UTF-8"))) {
        String linea;
        while((linea = buffer.readLine()) != null) {
            leido += linea + System.lineSeparator(); //Esto variará
        }
    } catch (Exception e) {
      e.printStackTrace();
    }
    
    return leido;
}
Para la escritura:
public static void escribir (String texto, String ruta) {
    
    try (BufferedWriter buffer = new BufferedWriter(
                new OutputStreamWriter(
                        new FileOutputStream(ruta), "UTF-8"))) {
        buffer.write(texto); //Esto variará
    } catch (Exception e) {
        e.printStackTrace();
    }
    
}
Nota: Si queremos guardar en varias lineas es mejor usar el método buffer.newLine() antes que usar la secuencia de escape "\n", pues hará el salto de línea correcto independientemente del SO donde se esté ejecutando (no es igual para Linux (\r\n) que para Windows (\n))

He encapsulado el código en métodos estáticos para que quede más claro el código para leer y el código para escribir.

Con esta sintaxis tienes optimizada la escritura de texto en ficheros y puedes elegir la codificación


Para practicar con una ruta relativa con un directorio, nos tenemos que crear una nueva carpeta en nuestro proyecto (New > Folder) que llamaremos datos. Cuando ejecutemos el código de abajo y refresquemos el proyecto (Refresh F5), nuestro fichero aparecerá dentro de ella con el nombre miTexto.txt.

Vamos a ejecutarlo con un ejemplo sencillo en nuestro main:
String ruta = "datos\\miTexto.txt";
String texto = "Primera linea.\nSegunda Linea.";

System.out.println("Guardo:\n" + texto);
escribir(texto, ruta);

String textoLeido = leer(ruta);
System.out.println("\nLeido:\n" + textoLeido);
De esta forma vemos que nuestro texto se guardo en disco y se ha leído devolviendo el mismo resultado.

Evidentemente no todo será guardar una cadena de texto formada y devolver un texto leído, pero con este ejemplo se ve la estructura básica en un ejemplo sencillo. Luego tocará cambiar la linea marcada con el comentario "Esto variara" con lo que haya que hacer al leer cada línea o al tener que guardar varias lineas distintas por ejemplo al guardar elementos de una colección.

La primera vez puede parecer complejo ver cómo se instancia nuestro buffer (es normal, le pasa a todo el mundo). Usándolo más veces al final todo tiene sentido, pero se soluciona fácilmente guardando el enlace donde se ve la sintaxis o simplemente haciéndonos nuestros propios métodos que encapsulen este código.

4 comentarios:

  1. Buenas tardes, solo comentar que para que funcione la creación de ficheros, en mi caso he tenido que importar la siguiente librería:

    import java.io.BufferedWriter;

    ResponderEliminar
    Respuestas
    1. Efectivamente cuando usas clases que no están en el paquete hay que importarlas. Normalmente al usar el asistente de contenido te lo importa en automático. Si no te saldrá un error y la ayuda te ofrecerá esa solución.

      La verdad que no está puesto en esta entrada porque en este punto del curso ya se ha tenido que hacer en otros ejemplos. Espero que no te haya hecho perder demasiado el tiempo.

      Un saludo.

      Eliminar
  2. Quería copiar estas:

    import java.io.BufferedWriter;
    import java.io.BufferedReader;
    import java.io.OutputStreamWriter;
    import java.io.InputStreamReader;
    import java.io.FileOutputStream;
    import java.io.FileInputStream;

    ResponderEliminar

Compárteme

Entradas populares