Java Tema 7
Ficheros de datos
En el desarrollo de aplicaciones reales, rara vez trabajamos solo con datos introducidos por el usuario durante la ejecución. Muchas veces es necesario guardar información para usarla más adelante (como resultados, configuraciones, listas de usuarios o registros), o bien leer datos almacenados previamente para procesarlos.
Recuerda que las variables se guardan en memoria RAM, por lo que cuando apagues tu app, los datos se borrarán. Para que la información sea persistente (no se borre), debemos guardarla en el Disco Duro (ficheros).
Lectura y escritura de datos#
Un flujo o Stream es un canal de comunicación por el que circulan datos desde un lugar hacia otro. Java clasifica los flujos en dos tipos principales:
- Flujos de entrada (Input): para que Java lea datos que vienen desde otro sitio (como un fichero).
- Flujos de salida (Output): para que Java escriba datos en otro sitio (como un fichero).
Los flujos de entrada y salida pueden manejar los datos en distintos formatos, lo cual implica que tengamos flujos de bytes y flujos de caracteres:
Tipos de flujos#
Dependiendo de lo que se quiera enviar/recibir, se pueden usar dos tipos de flujos (caminos) en Java:
| Tipo de flujo | Los datos viajan… | Ideal para | Para leer | Para escribir |
|---|---|---|---|---|
| De bytes | en binario | Imágenes, vídeos, ficheros comprimidos… | FileInputStream | FileOutputStream |
| De caracteres | como texto | Ficheros .txt, .csv y similares | FileReader | FileWriter |
En nuestro caso, utilizaremos por ahora solo Flujos de caracteres.
Mejora del rendimiento con Buffered#
Leer o escribir un fichero carácter a carácter es como llevar agua de un sitio a otro con una cucharilla: funciona, pero es lentísimo. Un Buffered es un cubo: va cargando muchos caracteres en memoria y los lleva de golpe.
Java tiene las dos versiones de cada clase. En la práctica, usaremos siempre la que lleva cubo:
| Clase básica (cucharilla) | Clase con buffer (cubo) | Para | Qué ganamos |
|---|---|---|---|
FileReader | BufferedReader | Leer texto | Lee una línea entera de golpe con readLine() |
FileWriter | BufferedWriter | Escribir texto | Escribe por bloques, y salta de línea con newLine() |
Fíjate en que la clase con buffer se construye envolviendo a la básica: new BufferedReader(new FileReader("texto.txt")). La de dentro abre el fichero; la de fuera le pone el cubo.
Excepciones y try con recursos#
Hay errores que no dependen de ti: el fichero no existe, el disco está lleno, no tienes permiso para escribir… Cuando pasa algo así, Java lanza una excepción y, si nadie la atiende, el programa se para. Con los ficheros, Java obliga a decir qué hacer si falla: si no lo haces, el programa ni siquiera compila.
Además, un fichero que se abre hay que cerrarlo al terminar, o puede quedarse a medio escribir.
Las dos cosas se resuelven a la vez con el try con recursos:
try (BufferedReader br = new BufferedReader(new FileReader("texto.txt"))) {
// aquí se usa el fichero
} catch (IOException e) {
// aquí, lo que se hace si algo falla
}
// aquí el fichero ya está cerrado, sin escribir nada más| Parte | Se lee como… | Qué va dentro |
|---|---|---|
try ( … ) | «Abre esto» | El fichero que vas a usar. Java lo cierra solo al terminar, haya error o no |
{ … } | «E intenta hacer esto con él» | El código que lee o escribe |
catch (IOException e) { … } | «Si algo falla, haz esto en vez de pararte» | Lo que queremos hacer si hay error, por ejemplo avisar al usuario. e.getMessage() dice qué ha pasado |
Es la forma que usaremos siempre que haya algo que abrir y cerrar: ficheros ahora, y conexiones a una base de datos más adelante.
Ejemplos de código#
Las clases de ficheros están en java.io, así que hay que importarlas arriba del todo:
import java.io.*;Ejemplo 1: leer un archivo de texto línea por línea con BufferedReader:
try (BufferedReader br = new BufferedReader(new FileReader("texto.txt"))) {
String linea;
// readLine() devuelve la siguiente línea, o null cuando ya no quedan más
while ((linea = br.readLine()) != null) {
System.out.println(linea);
}
} catch (IOException e) {
System.out.println("Error al leer el archivo: " + e.getMessage());
}Ejemplo 2: escribir varias líneas en un archivo de texto con BufferedWriter:
try (BufferedWriter bw = new BufferedWriter(new FileWriter("salida.txt", false))) {
bw.write("Primera línea");
bw.newLine();
bw.write("Segunda línea");
} catch (IOException e) {
System.out.println("Error al escribir en el archivo: " + e.getMessage());
}¡Ojo! Observa la primera línea donde se crea el Buffer, ¿ves un false? Ahí también se podría poner true. Veamos para qué sirve cada uno:
| Código | Modo | Qué pasa con lo que ya había en el fichero |
|---|---|---|
new FileWriter("archivo.txt", true) | Append (añadir) | Se conserva: lo nuevo se pega al final |
new FileWriter("archivo.txt", false) | Overwrite (sobrescribir) | Se borra: se escribe desde cero |
Si el fichero no existe, en los dos casos Java lo crea.
Errores frecuentes#
| Mensaje o síntoma | Qué significa | Cómo se arregla |
|---|---|---|
unreported exception FileNotFoundException; must be caught or declared to be thrown | Has abierto un fichero fuera de un try con recursos | Ábrelo dentro de los paréntesis del try, con su catch (IOException e) |
cannot find symbol en BufferedReader o FileReader | Falta importar las clases | Añade import java.io.*; arriba del todo |
FileNotFoundException: texto.txt (No such file or directory) (en Windows: El sistema no puede encontrar el archivo especificado) | Al ejecutar: Java no encuentra el fichero | El fichero tiene que estar en la carpeta del proyecto (la raíz, no dentro de src), y el nombre debe coincidir exactamente, extensión incluida |
| El fichero se crea, pero sale vacío | No se ha cerrado, y lo escrito se quedó en el cubo sin llegar al disco | Ábrelo con try con recursos, que lo cierra solo |
| Se han borrado los datos que ya había | Has abierto el FileWriter con false (o sin segundo valor), que sobrescribe | Para añadir al final, usa new FileWriter("archivo.txt", true) |