Ficheros de datos¶
Normalmente, cuando se codifica un programa, se hace con la intención de que ese programa pueda interactuar con los usuarios del mismo, es decir, que el usuario pueda pedirle que realice cosas y pueda suministrarle datos con los que se quiere que haga algo. Una vez introducidos los datos y las órdenes, se espera que el programa manipule de alguna forma esos datos para proporcionarnos una respuesta a lo solicitado.
Lectura y escritura secuencial en un archivo¶
En Java es posible utilizar dos tipos de ficheros (de texto o binarios) y dos tipos de acceso a los ficheros (secuencial o aleatorio). Los ficheros de texto están compuestos de caracteres legibles, mientras que los binarios pueden almacenar cualquier tipo de datos (int, float, boolean,...).
Una lectura secuencial implica tener que acceder a un elemento antes de acceder al siguiente, es decir, de una manera lineal (sin saltos). Sin embargo los ficheros de acceso aleatorio permiten acceder a sus datos de una forma aleatoria, esto es indicando una determinada posición desde la que leer/escribir.
Para tratar con un fichero siempre hay que actuar de la misma manera:
- Se abre el fichero.
- Se utiliza el fichero.
- Gestión de excepciones (opcional, pero recomendada)
- Se cierra el fichero y se destruye el objeto.
La clase File¶
La clase File (Fichero) se usa para obtener información sobre ficheros (también llamado archivos) y directorios. Además, permite crear esos ficheros y directorios. Un objeto de la clase File representa un archivo o directorio. Para hacer un de esta clase, seguiremos la siguiente estructura:
File nombreFichero = new File ("nombreFichero.extension"); // si se considera que esta
//en el mismo directorio
File nombreFichero = new File ("ruta/nombreFichero.extension"); // si se considera que
// no esta en el mismo directorio
File nombreFichero = new File ("ruta","nombreFichero.extension");
//Podemos hacer uso de File para crear directorios para ello:
File nombreRuta = new File ("ruta");
File nombreFichero = new File (nombreRuta,"nombreFichero.extension");
Info
En todos los casos la ruta o path puede ser absoluta o relativa. Debemos tener en cuenta que crear un objeto File no significa que deba existir el fichero o el directorio o que el path sea correcto. Si no existen no se lanzará ningún tipo de excepción, ni tampoco serán creados.
Es por ello que debemos conocer los métodos que nos proporciona la clase File para conocer lo que se puede desarrollar con ella. A continuación, se exponen algunos métodos que pueden ser de utilidad, pero es recomendable que se haga uso de la documentación oficial.
| Método | Descripción |
|---|---|
boolean createNewFile() |
Crea el fichero asociado al objeto File. Devuelve true si se ha podido crear o false si ya existe. Si ocurre un error I/O lanza una excepción del tipo IOException. |
boolean delete() |
Elimina el fichero o directorio. Si es un directorio debe estar vacío. Devuelve true si se ha podido eliminar. |
boolean exists() |
Devuelve true si el fichero o directorio existe. |
String getName() |
Devuelve el nombre del fichero o directorio. |
String getAbsolutePath() |
Devuelve la ruta absoluta asociada al objeto File. |
String getPath() |
Devuelve la ruta con la que se creó el objeto File. Puede ser relativa o no. |
String getParent() |
Devuelve un String conteniendo el directorio padre del File. Devuelve null si no tiene directorio padre. |
File getParentFile() |
Devuelve un objeto File conteniendo el directorio padre del File. Devuelve null si no tiene directorio padre. |
boolean isDirectory() |
Devuelve true si es un directorio válido. |
boolean isFile() |
Devuelve true si es un fichero válido. |
long length() |
Devuelve el tamaño en bytes del fichero. Devuelve 0 si no existe. Devuelve un valor indeterminado si es un directorio. |
String[] list() |
Devuelve un array de String con el nombre de los archivos y directorios que contiene el directorio indicado en el objeto File. Si no es un directorio devuelve null. Si el directorio está vacío devuelve un array vacío. |
String[] list(FilenameFilter filtro) |
Similar al anterior. Devuelve un array de String con el nombre de los archivos y directorios que contiene el directorio indicado en el objeto File que cumplen con el filtro indicado. |
boolean mkdir() |
Crea el directorio. Devuelve true si se ha podido crear. |
boolean renameTo(File dest) |
Cambia el nombre del fichero por el indicado en el parámetro dest. Devuelve true si se ha realizado el cambio. |
/*El siguiente programa muestra el uso de algunos métodos de la clase File.
Se crea un objeto File ruta asociado al directorio c:/ficheros y un objeto File f
asociado al fichero datos.txt que se encuentra en ese directorio.
Si el fichero y/o el directorio no existen se crean,
si el fichero ya existía se muestra el tamaño del mismo.*/
import java.io.File;
import java.io.IOException;
public class Ejemplo1 {
public static void main(String[] args) throws IOException {
File ruta = new File("c:/ficheros"); // ruta absoluta
File f = new File(ruta, "datos.txt"); // tercer constructor explicado
System.out.println(f.getAbsolutePath()); // path absoluto de datos.txt
System.out.println(f.getParent()); // path del directorio raíz de datos.txt
System.out.println(ruta.getAbsolutePath()); // path absoluto de c:/ficheros
System.out.println(ruta.getParent()); // path del directorio raíz de c:/ficheros
if (!f.exists()) { // se comprueba si el fichero existe o no
System.out.println("Fichero " + f.getName() + " no existe");
if (!ruta.exists()) { // se comprueba si la ruta existe o no
System.out.println("El directorio " + ruta.getName() + " no existe");
if (ruta.mkdir()) { // se crea la ruta. Si se ha creado correctamente
System.out.println("Directorio creado");
if (f.createNewFile()) { // se crea el fichero. Si lo crea correctamente
System.out.println("Fichero " + f.getName() + " creado");
} else {
System.out.println("No se ha podido crear " + f.getName());
}
} else {
System.out.println("No se ha podido crear " + ruta.getName());
}
} else { // si la ruta existe creamos el fichero
if (f.createNewFile()) {
System.out.println("Fichero " + f.getName() + " creado");
} else {
System.out.println("No se ha podido crear " + f.getName());
}
}
} else { // el fichero existe. Mostramos el tamaño
System.out.println("Fichero " + f.getName() + " existe");
System.out.println("Tamaño " + f.length() + " bytes");
}
}
}
/*El siguiente programa muestra cómo eliminar un fichero
y cómo cambiar el nombre de un fichero usando la clase File.
*/
import java.io.File;
import java.util.Scanner;
public class Ejemplo2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String nombre;
File ruta = new File("c:/ficheros"); // Ruta absoluta
// Eliminar un fichero
System.out.println("Introduce el nombre del fichero a eliminar: ");
nombre = sc.nextLine();
File f = new File(ruta, nombre); // Tercer constructor explicado
if (f.exists()) {
System.out.println(f.getAbsolutePath());
if (f.delete()) {
System.out.println("Fichero eliminado");
} else {
System.out.println("No se ha podido eliminar");
}
} else {
System.out.println("El fichero " + f.getAbsolutePath() + " no existe");
}
// Cambiar el nombre de un fichero
System.out.println("Introduce el nombre del fichero a renombrar: ");
nombre = sc.nextLine();
File f1 = new File(ruta, nombre);
if (f1.exists()) {
System.out.println(f1.getAbsolutePath());
System.out.println("Introduce nuevo nombre: ");
String nuevoNombre = sc.nextLine();
File f2 = new File(ruta, nuevoNombre);
if (f1.renameTo(f2)) {
System.out.println("Se ha cambiado el nombre");
} else {
System.out.println("No se ha podido cambiar el nombre");
}
} else {
System.out.println("El fichero " + f1.getAbsolutePath() + " no existe");
}
}
}
Filtros¶
Un filtro sirve para que el método list devuelva solo aquellos archivos o carpetas que cumplan una determinada condición (que sean de una extensión determinada, que contengan en su nombre cierta cadena, empiecen por una cadena, etc…). Un filtro es un objeto de una clase que implementa la interfaz FilenameFilter (Filtro de nombres de fichero). Esta interface tiene un solo método llamado accept que devuelve un valor de tipo boolean:
public interface FilenameFilter{ boolean accept (File ruta, String nombre); }
El método recibe el directorio donde se encuentra el archivo (objeto File) y el nombre del archivo (String). Este método lo utiliza el método list de File para decidir si un archivo o directorio determinado se incluye o no en el array que devuelve. Si accept devuelve true se incluye y si devuelve false no se incluye. El método list llama de forma automática al método accept para cada uno de los archivos o directorios.
import java.io.File;
import java.io.FilenameFilter;
//Clase Filtro implementa el interface FilenameFilter
class Filtro implements FilenameFilter {
String extension;
Filtro(String extension) {
this.extension = extension;
}
//implementación del método accept del interface
@Override
public boolean accept(File ruta, String nombre) {
return nombre.endsWith(extension);
}
}
Que de forma práctica se puede aplicar:
import java.io.File;
public class Ejemplo5 {
public static void main(String[] args) {
File ruta = new File("/temas/teoria"); // Ruta absoluta
System.out.println("Archivos .pdf en el directorio " + ruta.getAbsolutePath());
// Crear el filtro
String[] lista = ruta.list(new Filtro(".pdf"));
// Verificar y listar los archivos
if (lista == null) {
System.out.println("Total: 0 archivos");
} else {
for (int i = 0; i < lista.length; i++) {
System.out.println(lista[i]);
}
System.out.println("Total: " + lista.length);
}
}
}
Clases FileWriter y FileReader¶
FileWriter¶
Para escribir en un fichero de texto utilizaremos dos clases: FileWriter (Escritor de ficheros) y PrintWriter (Impresor con formato). La clase FileWriter permite tener acceso al fichero en modo escritura. Para crear objetos FileWriter podemos utilizar los constructores:
FileWriter fw = new FileWriter (Path);
FileWriter fw = new FileWriter (File Objeto);
FileWriter fw = new FileWriter (Path, append);
FileWriter fw = new FileWriter (File Objeto, append);
FileWriter proporciona el método write() para escribir cadenas de caracteres aunque lo normal es utilizar esta clase junto con la clase PrintWriter para facilitar la escritura. La clase PrintWriter permite escribir caracteres en el fichero de la misma forma que en la pantalla.
Si lo queremos ver en un ejemplo:
FileWriter fw = new FileWriter("c:/ficheros/datos.txt");
PrintWriter salida = new PrintWriter("c:/ficheros/datos.txt");
FileNotFoundException.
Una vez creado el objeto PrintWriter podemos utilizar print(), println() y printf() para escribir en el fichero como si fuese en pantalla
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.Scanner;
public class Ejemplo6 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
PrintWriter salida = null;
try {
salida = new PrintWriter("c:/ficheros/datos.txt");
String cadena;
System.out.println("Introduce texto. Para acabar introduce la cadena FIN:");
cadena = sc.nextLine();
while (!cadena.equalsIgnoreCase("FIN")) {
salida.println(cadena);
cadena = sc.nextLine();
}
salida.close();
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
}
}
}
FileReader¶
Para leer en un fichero de texto utilizaremos dos clases: FileReader (Lector de ficheros) y BufferedReader (Lector con buffer). La clase FileReader permite tener acceso al fichero en modo lectura.
FileReaderfw = new FileReader (Path);
FileReaderfw = new FileReader (File Objeto);
FileNotFoundException si el fichero no existe. La clase FileReader proporciona el método read() para leer caracteres del fichero aunque lo normal es realizar la lectura mediante la clase BufferedReader. Para leer utilizando la clase BufferedReader se debe crear un objeto BufferedReader a partir de un objeto FileReader.
FileReader fr = new FileReader("c:/ficheros/datos.txt");
BufferedReader entrada = new Buffered(fr);
BufferedReader podemos utilizar:
- El método
readLine()para leer líneas de texto del fichero (String). Este método devuelve null cuando no hay más líneas para leer. - El método
read()para leer carácter a carácter. Devuelve un entero que representa el código Unicode del carácter leído. Devuelve -1 si no hay más caracteres.
Ambos métodos lanzan una excepción IOException si ocurre un error de lectura. El fichero se debe cerrar cuando ya no se use, mediante el método close(). Este método lanza una excepción IOException.
import java.io.BufferedReader; // Para leer caracteres almacenados en un buffer
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Ejemplo7 {
public static void main(String[] args) {
FileReader fr = null;
try {
fr = new FileReader("c:/ficheros/datos.txt");
BufferedReader entrada = new BufferedReader(fr);
String cadena = entrada.readLine();
while (cadena != null) {
System.out.println(cadena);
cadena = entrada.readLine();
}
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
} catch (IOException e) {
System.out.println(e.getMessage());
} finally {
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
}
Flujos de datos OutputStream y InputStream¶
OutputStream¶
Para escribir datos en un fichero binario utilizaremos las clases FileOutputStream (Flujo de salida de ficheros) y DataOutputStream (Flujo de salida de datos), derivadas de OutputStream (Flujo de salida).
La clase FileOutputStream permite tener acceso al fichero para escribir bytes. El fichero se crea y si ya existe su contenido se pierde. Si lo que necesitamos es abrir un fichero binario existente sin perder su contenido y añadir más contenido al final utilizaremos los constructores con el append correspondiente.
FileOutputStream fos = new FileOutputStream (Path, append);
FileOutputStream fos = new FileOutputStream (File Objeto, append);
write() para escribir bytes en el fichero. Este método lanza una IOException.
A partir de un objeto FileOutputStream se puede crear un objeto DataOutputStream, que proporciona métodos para escribir datos de tipo primitivo en el fichero. La clase proporciona métodos writeXxx() donde Xxx es el nombre del tipo primitivo. Lanza una IOException.
Ejemplo: Programa que lee enteros por teclado y los escribe en el fichero datos.dat. La lectura de datos acaba cuando se introduce -1.
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
public class Ejemplo12 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
FileOutputStream fos = null;
DataOutputStream salida = null;
try {
fos = new FileOutputStream("c:/ficheros/datos.dat");
salida = new DataOutputStream(fos);
System.out.print("Introduce número entero. -1 para acabar: ");
int n = sc.nextInt();
while (n != -1) {
salida.writeInt(n); // Se escribe el número entero en el fichero
System.out.print("Introduce número entero. -1 para acabar: ");
n = sc.nextInt();
}
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
} catch (IOException e) {
System.out.println(e.getMessage());
} finally {
try {
if (fos != null) {
fos.close();
}
if (salida != null) {
salida.close();
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
}
InputStream¶
Para leer de un fichero binario utilizaremos las clases FileInputStream (Flujo de entrada de ficheros) y DataInputStream (Flujo de entrada de datos), derivadas de InputStream (Flujo de entrada).
La clase FileInputStream permite leer bytes de un fichero. Ambos lanzan una excepción FileNotFoundException si el fichero no existe. La clase proporciona el método read() para leer bytes del fichero. El método read() lanza una excepción IOException.
FileInputStream fis = new FileInputStream (Path);
FileInputStream fis = new FileInputStream (File Objeto);
La clase proporciona métodos readXxx() donde Xxx es el nombre del tipo primitivo. Lanzan una excepción IOException.
Ejemplo: Programa que lee el contenido del fichero creado en el ejemplo anterior. Utilizaremos un bucle infinito para leer datos. Cuando se llega al final del fichero se lanza la excepción EOFException que se utiliza para salir del bucle while.
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Ejemplo14 {
public static void main(String[] args) {
FileInputStream fis = null;
DataInputStream entrada = null;
try {
fis = new FileInputStream("c:/ficheros/datos.dat");
entrada = new DataInputStream(fis);
int n;
while (true) {
n = entrada.readInt(); // Se lee un entero del fichero
System.out.println(n); // Se muestra en pantalla
}
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
} catch (EOFException e) {
System.out.println("Fin de fichero");
} catch (IOException e) {
System.out.println(e.getMessage());
} finally {
try {
if (fis != null) {
fis.close();
}
if (entrada != null) {
entrada.close();
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
}
Lectura y escritura aleatoria en un archivo¶
La clase RandomAccessFile (Fichero de acceso aleatorio) se utiliza para acceder a un fichero sobre cualquier posición.
RandomAccessFile fichero = new RandomAccessFile("ruta", "modo");
RandomAccessFile fichero = new RandomAccessFile(Objeto, "modo");
FileNotFoundException. El argumento mode indica el modo de acceso en el que se abre el fichero. Los valores permitidos para este parámetro son:
| Modo | Significado |
|---|---|
r |
Abre el fichero en modo solo lectura. El fichero debe existir. Una operación de escritura en este fichero lanzará una excepción IOException. |
rw |
Abre el fichero en modo lectura y escritura. Si el fichero no existe se intentará crear. |
Abrir un fichero aleatorio para lectura. Se abre el fichero clientes.dat para lectura usando el primer constructor.
RandomAccessFile fichero = new RandomAccessFile("/ficheros/clientes.dat", "r");
File f = new File ("/ficheros/personas.dat");
RandomAccessFile fichero = new RandomAccessFile(f, "rw")
| Método | Significado |
|---|---|
long getFilePointer() |
Devuelve la posición actual del puntero del fichero. Indica la posición (en bytes) donde se va a leer o escribir. |
long length() |
Devuelve la longitud del fichero en bytes. |
void seek(long pos) |
Coloca el puntero del fichero en una posición pos determinada. La posición se da como un desplazamiento en bytes desde el comienzo del fichero. La posición 0 indica el principio del fichero y la posición length() indica el final del fichero. |
int read() |
Devuelve el byte leído en la posición marcada por el puntero. Devuelve -1 si alcanza el final del fichero. Se debe utilizar este método para leer los caracteres de un fichero de texto. |
final String readLine() |
Devuelve la cadena de caracteres que se lee, desde la posición marcada por el puntero, hasta el siguiente salto de línea que se encuentre. El modificador final indica que no puede ser sobrescrito. |
xxx readXxx() |
Hay un método read para cada tipo de dato básico: readChar, readInt, readDouble, readBoolean, etc. |
void write(int b) |
Escribe en el fichero el byte indicado por parámetro. Se debe utilizar este método para escribir caracteres en un fichero de texto. |
final void writeBytes(String s) |
Escribe en el fichero la cadena de caracteres indicada por parámetro. |
final void writeXxx(argumento) |
También existe un método write para cada tipo de dato básico: writeChar, writeInt, writeDouble, writeBoolean, etc. |
Programa que pide un número entero por teclado y lo añade al final de un fichero binario enteros.dat que contiene números enteros. El programa utiliza el método mostrarFichero() que se llama dos veces. La primera muestra el contenido del fichero antes de añadir el nuevo número y la segunda llamada muestra el fichero después de añadirlo.
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Scanner;
public class Ejemplo16 {
static Scanner sc = new Scanner(System.in);
static RandomAccessFile fichero = null;
public static void main(String[] args) {
int numero;
try {
// Se abre el fichero para lectura y escritura
fichero = new RandomAccessFile("c:/ficheros/enteros.dat", "rw");
mostrarFichero(); // Muestra el contenido original del fichero
System.out.print("Introduce un número entero para añadir al final del fichero: ");
numero = sc.nextInt(); // Se lee el entero a añadir en el fichero
fichero.seek(fichero.length()); // Nos situamos al final del fichero
fichero.writeInt(numero); // Se escribe el entero
mostrarFichero(); // Muestra el contenido del fichero después de añadir el número
fichero.close();
} catch (FileNotFoundException ex) {
System.out.println(ex.getMessage());
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
// Método para mostrar los datos del fichero
public static void mostrarFichero() {
int n;
try {
fichero.seek(0); // Nos situamos al principio del fichero
while (true) {
n = fichero.readInt(); // Se lee un entero del fichero
System.out.println(n); // Se muestra en pantalla
}
} catch (EOFException e) {
System.out.println("Fin de fichero");
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}
Programa Java para modificar un entero dentro del fichero enteros.dat con acceso aleatorio. Para ello se pide la posición que ocupa el entero a modificar dentro del fichero, a continuación, se lee y muestra el valor actual, se pide el nuevo valor y finalmente se escribe el nuevo valor en la posición indicada, modificando de esta forma el valor antiguo por el nuevo. La posición deberá estar comprendida entre 1 y el número de enteros que contiene el fichero. La cantidad de enteros que contiene el fichero se calcula así:
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Scanner;
public class Ejemplo17 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
RandomAccessFile fichero = null;
int pos, numero;
long size;
try {
// Abrir el fichero para lectura y escritura
fichero = new RandomAccessFile("c:/ficheros/enteros.dat", "rw");
// Calcular cuántos enteros tiene el fichero
size = fichero.length();
size = size / 4; // Cada entero ocupa 4 bytes
System.out.println("El fichero tiene " + size + " enteros");
// Modificar el entero que se encuentra en una posición determinada
do {
System.out.println("Introduce una posición (>= 1 y <= " + size + "): ");
pos = sc.nextInt();
} while (pos < 1 || pos > size);
pos--; // Ajustar a la indexación 0 (la posición 1 realmente es la 0)
// Nos situamos en la posición (byte de inicio) del entero a modificar
fichero.seek(pos * 4); // Cada entero ocupa 4 bytes
// Leemos y mostramos el valor actual
System.out.println("Valor actual: " + fichero.readInt());
// Pedimos que se introduzca el nuevo valor
System.out.println("Introduce nuevo valor: ");
numero = sc.nextInt();
// Nos situamos de nuevo en la posición del entero a modificar
fichero.seek(pos * 4);
// Escribimos el entero
fichero.writeInt(numero);
fichero.close();
} catch (FileNotFoundException ex) {
System.out.println(ex.getMessage());
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}