6 Fichero de Acceso Aleatorio¶
Introducción¶
Hasta ahora, todas las operaciones que se han realizado sobre los ficheros se realizaban de forma secuencial, es decir, se empezaba la lectura en el primer dato (byte, carácter u objeto), y seguidamente se leían los siguientes uno a continuación del otro hasta terminar el fichero. De la misma forma se hacía la escritura.
Una de las desventajas que supone esto es que si se desea acceder a uno los elementos intermedios o se quiere insertar un elemento en medio del fichero, habría que leer o escribir de forma secuencial el fichero hasta la posición de inserción o lectura, y luego realizar la operación, lo que lo hace bastante trabajoso.
Java dispone de la clase RandomAccessFile
que dispone de métodos para acceder al contenido de un fichero binario de forma aleatoria, es decir, no secuencial; para posicionarnos en una posición concreta del mismo. Esta clase no hereda de InputStream/OutputStream, ya que su comportamiento es totalmente distinto puesto que se puede avanzar y retroceder dentro de un fichero.
Clase RandomAccessFile¶
La clase RandomAccessFile es una clase de Java que permite acceder al contenido de un fichero posicionándose en una posición concreta del mismo. Se dice que se accede de forma aleatoria y no secuencial.
La clase dispone de dos constructores para crear el fichero de acceso aleatorio (puede lanzar la excepción FileNotFoundException):
RandomAccessFile(String nombreFichero, String modoAcceso)
: Contiene la cadena del nombre del fichero incluido en el path.RandomAccessFile(File file, String modoAcceso)
: Contiene el objeto File asociado a un fichero.
El argumento modoAcceso puede tener dos valores:
- r → Abre el fichero en modo de solo lectura, por lo que debe existir. Una operación de escritura en este fichero lanzará la excepción IOException.
- rw → Abre el fichero en modo lectura y escritura. En caso de que no exista dicho fichero, se crea.
Una vez abierto el fichero pueden usarse los métodos readXXX
y writeXXX
de las clases DataInputStream y DataOutputStream.
La clase RandomAccessFile maneja un puntero que indica la posición actual en el fichero. Cuando el fichero se crea el puntero al fichero se coloca en 0, apuntando al principio del mismo. Las sucesivas llamadas a los métodos read() y write() ajustan el puntero según la cantidad de bytes leídos o escritos.
Los métodos más importantes son:
Método | Función |
---|---|
long getFilePointer() | Devuelve la posición actual del puntero del fichero. |
void seek(long posición) | Coloca el puntero del fichero en una posición determinada desde el comienzo del mismo. |
long length() | Devuelve el tamaño del fichero en bytes. La posición length() marca el final del fichero. |
int skipBytes(int desplazamiento) | Desplaza el puntero desde la posición actual el número de bytes indicados en desplazamientos. |
Escritura de archivos de acceso aleatorio¶
Este ejemplo inserta datos de empleados en un fichero aleatorio. Los datos a insertar son: apellido, departamento y salario, que se obtienen de arrays que se llenan en el programa, los datos se van introduciendo de forma secuencial por lo que no va a ser necesario usar método seek(). Por cada empleado también se insertará un identificador (mayor que 0) que coincidirá con el índice +1 con el que se recorren los arrays. La longitud del registro de cada empleado es la misma (36 bytes) y los tipos se insertan y su tamaño en bytes es el siguiente:
- Se inserta en primer lugar un entero, que es el identificador, ocupa 4 bytes.
- A continuación una cadena de 10 caracteres, es el apellido. Como Java utiliza caracteres UNICODE, cada carácter de una cadena de caracteres ocupa 16 bits (2 bytes), por lo tanto, el apellido ocupa 20 bytes.
- Un tipo entero que es el departamento, ocupa 4 bytes.
- Un tipo double que es el salario, ocupa 8 bytes.
Tamaños de otros tipos: short (2 bytes), byte (1 byte), long (8 bytes), boolean (1 byte), float (4 bytes), etc.
El fichero se abre en modo "rw" para lectura y escritura.
public class Write {
public void show() throws IOException {
File f = new File("employee.dat");
RandomAccessFile randomFile = new RandomAccessFile(f, "rw");
String[] apellido = { "FERNANDEZ", "GIL", "LOPEZ", "RAMOS", "SEVILLA", "CASILLA", "REY"};
int[] dep = {10,20,10,10,30,30,20};
double[] salario = {1000.45, 2400.60, 3000.0, 1500.56, 2200.0, 1435.87, 2000.0};
StringBuilder buffer = null;
int n = apellido.length;
for(int i = 0; i < n; i++){ // Se recorre los arrays
randomFile.writeInt(i+1); // Uso i+1 para identificar al empleado
buffer = new StringBuilder(apellido[i]);
buffer.setLength(10); // 10 caracteres para el apellido
randomFile.writeChars(buffer.toString()); // Insertar apellido
randomFile.writeInt(dep[i]); // Insertar departamento
randomFile.writeDouble(salario[i]); // Insertar salario
}
randomFile.close(); // Cierre de fichero
}
public static void main(String[] args) throws IOException {
new Write().show();
}
}
Para acceder a un registro específico, podemos usar la siguiente formula siendo n el número de registro que quiero acceder.
Fórmula de acceso $$ (n-1)*36 $$
Lectura de ficheros de acceso aleatorio¶
Ahora se visualiza todos los registros. El posicionamiento para empezar a recorrer los registros empieza en 0, para recuperar los siguientes registros hay que sumar 36 (tamaño del registro) a la variable utilizada para el posicionamiento.
public class Read {
public void show() throws IOException {
File f = new File("files/EmployerRandom.dat");
RandomAccessFile randomFile = new RandomAccessFile(f, "r"); // Solo lectura
int id, dep, posicion;
double salario;
char[] apellido = new char[10];
String apellidos;
char aux;
posicion = 0; // se sitúa al principio
do {
randomFile.seek(posicion); // se posiciona en posición
id = randomFile.readInt(); // se obtiene id del empleado
// se recorre uno a uno los caracteres del apellido
for (int i = 0; i < apellido.length; i++) {
aux = randomFile.readChar();
apellido[i] = aux;
}
// convierte a String el array
apellidos = new String(apellido);
dep = randomFile.readInt(); // obtiene el departamento
salario = randomFile.readDouble(); // obtiene el salario
if (id > 0) {
System.out.printf("ID: %s, Apellido: %s, Departamento: %d, Salario: %.2f \n",
id, apellidos.trim(), dep, salario);
}
// se posiciona para el siguiente empleado
posicion = posicion + 36;
// si se ha recorrido TO DO los bytes se sale del bucle
} while (randomFile.getFilePointer() != randomFile.length());
randomFile.close();
}
}
Consultar datos¶
Para consultar datos determinados de un fichero no es necesario recorrer todos los registros del fichero, conociendo su identificador se puede acceder a la posición que ocupa dentro del mismo y así obtener sus datos.
OJO
Es recomendable que si se va a leer/escribir de forma secuencial NO se maneje el puntero
Por ejemplo, se supone que se desean obtener los datos del empleado con identificador 5, para calcular la posición se ha de tener en cuenta los bytes que ocupa cada registro, en este caso 36:
public class Select {
public void show() throws IOException {
File f = new File("files/EmployerRandom.dat");
RandomAccessFile file = new RandomAccessFile(f, "r"); // Solo lectura
int id, dep;
double salario;
char[] apellido = new char[10];
String apellidos;
char aux;
int identificador = 5;
int position = (identificador - 1) * 36;
if (position >= file.length()) {
System.out.printf("ID: %d, NO EXISTE EMPLEADO...\n", identificador);
} else {
file.seek(position); // se posiciona
id = file.readInt(); // obtengo el id del empleado
// se recorre uno a uno los caracteres del apellido
for (int i = 0; i < apellido.length; i++) {
aux = file.readChar();
apellido[i] = aux;
}
// convierto a String el array
apellidos = new String(apellido);
dep = file.readInt(); // obtiene el departamento
salario = file.readDouble(); // obtiene el salario
if (id > 0) {
System.out.printf("ID: %s, Apellido: %s, Departamento: %d, Salario: %.2f \n",
id, apellidos.trim(), dep, salario);
}
}
}
public static void main(String[] args) throws IOException {
new Select().show();
}
}
Insertar datos¶
Para añadir un registro a partir del último insertado, hay que posicionarse al final del fichero.
Para insertar un nuevo registro se aplica la función al identificador para calcular la posición.
StringBuffer buffer = null; // buffer para almacenar el apellido
String apellido = "GONZALEZ"; // apellido a insertar
double salario = 1230.97; // salario
int id = 20; // id del empleado
int dep = 10; // departamento del empleado
long posicion = (id -1)*36; // se calcula la posición
file.seek(posicion); // se posiciona
file.writeInt(id); // escribe la id
buffer = new StringBuffer(apellido);
buffer.setLength(10); // 10 caracteres para el apellido
file.writeChars(buffer.toString()); // inserta el apellido
file.writeInt(dep); // inserta el departamento
file.writeDouble(salario); //inserta salario
file.close(); // cierra el fichero
Modificar un registro¶
Para modificar un registro, se accede a la posición y se efectúa las modificaciones. Es necesario que el fichero se abra en modo rw.
int registro = 4; // id a modificar
long posicion = (registro - 1) * 36; // calculo la posicion
posicion = posicion + 4 + 20;// sumo el tamaño de ID + apellido
file.seek(posicion); // se posiciona
file.writeInt(40); // modifico el departamento
file.writeDouble(4000.87); // modifico el salario
writeUTF¶
El método writeUTF()
me permite escribir una cadena completa en un fichero aleatorio. Tenemos que tener en cuenta que para cadena ocupará siempre 2 bytes de más, ya que también almacenada el tamaño de la cadena.
int stringSize = 10;
int intSize = 2;
int totalSize = stringSize+intSize+2;
randomAccess.writeUTF(String.format("%-"+stringSize"s", "Holaa"));
¡Importante!
Cuando el tamaño máximo a escribir, es mayor que el tamaño real de la cadena, se escribirán null. En el ejemplo anterior el tamaño máximo de cadena es de 10, mientras que la cadena "Holaa", es de tamaño 5, por lo que se escribiría "HolaaNULLNULLNULLNULLNULL", 5 null para poder llegar al máximo, aunque esto puede ser un problema.
Si queremos escribir una cadena con writeUTF tendremos que formatear la cadena de tal forma que me escriba espacios en blanco en vez de null, para ello sería útil usar String.format(), con el tamaño de la cadena, en negativo, para que ponga los espacios a la derecha de la cadena.