Advertising:
Leer y escribir ficheros de texto con java 8
Con Java 8, aparecen nuevas clases, sintaxis y formas de hacer las cosas que permiten, entre otras, leer y escribir ficheros de texto de forma más sencilla que la tradicional de Java.
Leer fichero de texto[edit]
Abrir el fichero[edit]
Tenemos varias formas de hacerlo. Una muy al estilo de java 8 consiste en usar la clase Files
y su método lines()
, de esta manera
Path path = Paths.get("fichero.txt");
Stream<String> stream = Files.lines();
Hemos creado una instancia de Path
con el nombre de fichero usando el método Paths.get()
. Luego con la clase Files
, obtenemos un 'stream' o flujo de líneas (de String
). Estas líneas no se cargan todas en memoria, sino que se irán leyendo del fichero según las vayamos necesitando.
Leer del Stream[edit]
La clase Stream
tiene muchos métodos que nos permiten hacer cosas con esas líneas, como filtrarlas, crear nuevos flujos o 'stream' modificados, etc, etc. Sin embargo, nos centramos sólo en leer y para ello nos viene bien el método forEach()
stream.forEach(System.out::println);
forEach()
admite como parámetro una clase que implemente la interface Consumer
. Sin embargo, aprovechando la sintaxis de Lambdas de java 8, podemos "crear" sobre la marcha una clase anónima que implemente esa interface.
Una forma es la que acabamos de usar. Basta con referenciar a un método de cualquier clase o intancia que tenga un método que admita un String
como parámetro. Por ejemplo, el método System.out.println()
. ¿cómo lo referenciamos?. Ponemos el nombre de la clase (si el método es estático) o de una instancia de esa clase (System.out
en nuestro ejemplo), dos veces dos puntos ::
y el nombre del método sin paréntesis ni nada (println
en nuestro ejemplo.
Otra opción es usar una expresión Lambda típica de java 8
stream.forEach((s)->System.out.println(s));
donde ponemos entre paréntesis el nombre del parámetro que vamos a recibir que será de tipo String
puesto que el Stream
es de tipo String
, una flecha ->
y lo que queremos hacer con ese String
. Debemos poner llaves si tenemos más de una linea de código. En este caso, sólo tenemos una línea para sacar por pantalla la línea del fichero.
Tratamiento de excepciones. try-with-resources[edit]
Tanto la apertura del fichero como la lectura, pueden lanzar excepciones que debemos tratar. En java anterior a 8, debemos montar un try-catch de la siguiente forma
BufferedReader br = null;
try {
// Abrir fichero y leer del fichero en un bucle
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != br) {
try {
br.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
Es bastante liado. Hay que poner un try-catch y para asegurarnos que el fichero se cierra tanto si salta excepción como si no, se pone el cierre en un finally. Para que la variable BufferedReader
esté accesible en el finally, debemos declararla fuera del try-catch, pero el fichero debe abrirse dentro del try, por si salta excepción. Esto hace que en el finally la variable BufferedReader
pueda ser null y necesitamos comprobarlo. Y para liarlo aún más, la llamada a close()
también lanza una excepción, por lo que necesitamos anidar un segundo bloque try-catch.
En java 8 existe una forma más sencilla de hacer todo esto, conocida como 'try-with-resources'. Es un try que admite entre paréntesis la apertura de un recurso (fichero, socket, ...) que implemente la interfaz AutoCloseable
. Detrás del try, entre paréntesis, se pone la apertura del recurso y el mismo try se encargará de cerrarlo cuando termine, tanto si salta excepción como si no, como haría el finally. Nuestro código quedará entonces, de forma completa, así
Path path = Paths.get("fichero.txt");
try (Stream<String> stream = Files.lines(path)) {
stream.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
Stream
implementa AutoCloseable
, por lo que el try se encargará de cerrarlo automáticamente cuando termine.
CharSet[edit]
El fichero de texto puede usar cualquier codificación de caracteres. Si al abrir el fichero usamos el método que hemos indicado, se supone que el fichero está en UTF-8. Sin embargo, el método Files.lines()
admite un segundo parámetro con el CharSet
que queramos usar. Por ejemplo
Path path = Paths.get("fichero.txt");
try (Stream<String> stream = Files.lines(path,Charset.defaultCharset())) {
...
} ...
usará la codificación de caracteres por defecto del sistema operativo.
Escribir fichero de texto[edit]
La parte más específica de java 8 ya está contada, por lo que escribir un fichero de texto requiere menos explicación. Únicamente mencionar que al escribir un fichero de texto no hay un flujo Stream
de líneas, ya que el fichero no está creado. El código sencillo puede ser este
String[] lines = new String[] { "line 1", "line 2", "line 2" };
Path path = Paths.get("outputfile.txt");
try (BufferedWriter br = Files.newBufferedWriter(path,
Charset.defaultCharset(), StandardOpenOption.CREATE)) {
for (String line : lines) {
br.write(line);
br.newLine();
}
} catch (Exception e) {
e.printStackTrace();
}
Declaramos un array de String
para escribir en el fichero. Esto es para el ejemplo, en un código real los String
pueden venir de cualquier sitio.
Abrimos el fichero como antes, en un try-with-resources, pero obteniendo un BufferedWriter
. En la apertura, además del Path
y del CharSet
, hemos puesto la opción StandardOpenOption.CREATE
. Esto crea el fichero si no existe, o lo sobreescribe si existe. Hay opciones, entre otras, para que proteste si el fichero existe StandardOpenOption.CREATE_NEW
o para que añada sin sobreescribir StandardOpenOption.APPEND
.
A partir de ahí, un bucle normal sobre el array de líneas para ir escribiéndolas en el fichero.
A lo java 8[edit]
No trata de la temática de escribir fichero en java 8, pero otra forma de recorrer el array, más al estilo de java 8, es usando los Stream
que usamos antes. La clase Arrays
nos permite obtener un Stream
de cualuier array, así
String [] lines = ....
Stream<String> stream = Arrays.stream(lines);
y ahora, al igual que hicimos antes, podemos usar forEach
para ir escribiendo en el fichero. Hay un problema en este caso y es que las operaciones de escritura pueden lanzar una IOException
, que debemos tratar en el método que pasemos al Stream
. El código completo con una expresión Lambda quedaría así
String[] lines = new String[] { "line 1", "line 2", "line 2" };
Path path = Paths.get("outputfile.txt");
try (BufferedWriter br = Files.newBufferedWriter(path,
Charset.defaultCharset(), StandardOpenOption.CREATE)) {
Arrays.stream(lines).forEach((s) -> {
try {
br.write(s);
br.newLine();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
} catch (Exception e) {
e.printStackTrace();
}
Nos fijamos sólo en el interior de la expresión Lambda. Escribimos la línea con br.write()
y un salto de línea con br.newLine
. Como esto puede soltar una excepción, la capturamos y, o bien la silenciamos sacando su traza por pantalla o algo, o bien como en este caso, la relanzamos como UncheckedIOException
, que no es necesario capturarla, al ser Unchecked.
Github[edit]
Tienes código de ejemplo de este tutorial en Github
Enlaces[edit]
- Lectura y Escritura de Ficheros en Java
- Búsqueda de ficheros
- Ficheros XML
- La clase File
- Un buen tutorial de java nio en inglés.