Acceso aleatorio a ficheros¶

Todos los flujos de E/S que hemos usado hasta ahora se conocen como flujos de solo lectura o solo escritura. Estos flujos se denominan flujos secuenciales en Java.
Un fichero que se lee o escribe mediante un flujo secuencial se denomina fichero de acceso secuencial. Los datos de un fichero de acceso secuencial NO se pueden actualizar.
Por lo tanto, para leer y escribir datos simultáneamente, Java proporciona la clase RandomAccessFile. Con esta clase, podemos leer y escribir datos en cualquier ubicación del fichero. Los archivos de acceso aleatorio son útiles para muchas aplicaciones diferentes.
Puntero de la clase RandomAccessFile¶
Un fichero de acceso aleatorio consta de una secuencia de bytes. Éstos, admiten un puntero especial conocido como puntero de fichero (file pointer). El puntero indica la posición actual (ubicación) en el fichero.
Se coloca en uno de estos bytes en el fichero y se puede mover a cualquier posición arbitraria antes de leer o escribir.
En otras palabras, se lleva a cabo una operación de lectura o escritura en la ubicación del puntero.
El puntero se puede mover utilizando el método seek().
Cuando se crea un fichero por primera vez, el puntero se establece en 0, lo que indica el comienzo del archivo. Cuando leemos o escribimos datos en el archivo usando métodos de lectura o escritura, el puntero del archivo avanza al siguiente elemento de datos (es decir, el siguiente byte).
Por ejemplo, si leemos un valor int usando el método readInt() del archivo, JVM lee 4 bytes usando el puntero, y ahora el puntero del archivo está 4 bytes por delante de la posición anterior, como se muestra en la figura a continuación.

RandomAccessFile raf = ....
raf.seek(position); //mueve el puntero a una posición
raf.seek(0); //mueve el puntero al inicio del fichero
raf.seek(raf.length()); //mueve el puntero al final del fichero
Constructor de la clase RandomAccessFile¶
Para construir un objeto de la clase tenemos que especificar el modo (mode) que determina qué tipo de acceso a ficheros está permitido.
| Modo | Significado | ¿Permite escribir? | ¿Sincroniza inmediatamente en disco? | Ejemplo de uso |
|---|---|---|---|---|
"r" |
Solo lectura | ❌ No | ❌ No aplica | new RandomAccessFile("datos.dat", "r"); |
"rw" |
Lectura y escritura | ✅ Sí | ❌ No (usa buffer del sistema) | new RandomAccessFile("datos.dat", "rw"); |
"rws" |
Lectura y escritura sincronizada | ✅ Sí | ✅ Sí (datos y metadatos) | new RandomAccessFile("datos.dat", "rws"); |
RandomAccessFile raf = new RandomAccessFile("myfile.dat", "rw");
Ejemplo de un programa que añade texto al final de un fichero¶
/**
* Abre un fichero usando RandomAccessFile en modo "rw"
* (lectura y escritura).
*
* - Mueve el puntero al final del fichero.
* - Añade la palabra "Java".
* - Se cierra automáticamente gracias a try-with-resources.
*/
public static void main(String[] args) {
// try-with-resources:
// El RandomAccessFile se cerrará automáticamente
// al salir del bloque try
try (RandomAccessFile file =
new RandomAccessFile("file.txt", "rw")) {
// file.length() devuelve el tamaño actual del fichero en bytes
// seek() mueve el puntero interno a la posición indicada
// Aquí lo movemos al final para añadir texto (append)
file.seek(file.length());
// writeBytes escribe la cadena en formato byte
// "\nJava" añade salto de línea + texto
file.writeBytes("\nJava");
} catch (IOException e) {
// Se captura cualquier error de entrada/salida
throw new RuntimeException(e);
}
}
Escribir números
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* Escribe los números 10, 20, 30 y 40
* en el fichero "numeros.dat".
*
* Cada número se guarda como int (4 bytes).
*/
public static void escribirNumeros() {
try (RandomAccessFile file =
new RandomAccessFile("numeros.dat", "rw")) {
// Nos aseguramos de empezar desde el inicio
file.seek(0);
// Escribimos los números como enteros (4 bytes cada uno)
file.writeInt(10);
file.writeInt(20);
file.writeInt(30);
file.writeInt(40);
System.out.println("Números escritos correctamente.");
} catch (IOException e) {
e.printStackTrace();
}
}
| Número | Bytes |
|---|---|
| 10 | 0–3 |
| 20 | 4–7 |
| 30 | 8–11 |
| 40 | 12–15 |
leer los números
/**
* Lee los números del fichero "numeros.dat"
* y los muestra por pantalla.
*/
public static void leerNumeros() {
try (RandomAccessFile file =
new RandomAccessFile("numeros.dat", "r")) {
// Mientras no lleguemos al final del fichero
while (file.getFilePointer() < file.length()) {
int numero = file.readInt();
System.out.print(numero + " ");
}
} catch (IOException e) {
e.printStackTrace();
}
}
import java.io.IOException;
import java.io.RandomAccessFile;
public class ModificarNumero {
public static void main(String[] args) {
try (RandomAccessFile file =
new RandomAccessFile("numeros.dat", "rw")) {
// Supongamos que el fichero tiene:
// 10, 20, 30, 40
int posicion = 2; // tercer número (empieza en 0)
// Cada int ocupa 4 bytes
file.seek(posicion * 4);
// Sobrescribimos el valor
file.writeInt(999);
//el fichero quedaría:
// 10, 20, 999, 40
System.out.println("Número modificado correctamente.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Contacto {
//tamaño fijo de los campos
final static int LONGITUD_NOMBRE = 20;
final static int LONGITUD_DIRECCION = 30;
final static int LONGITUD_TELEFONO = 10;
//tamaño de registro. Es necesario conocer el tamaño para mover el cursor del fichero correctamente
final static int SIZE_REGISTRO =4+//tamaño int
//tamaño de los string
(LONGITUD_NOMBRE + LONGITUD_DIRECCION + LONGITUD_TELEFONO)
+6;//2 bytes por cada string que guarda el tamaño del string en el fichero
private int id;
private String nombre;
private String direccion;
private String telefono;
public Contacto(int id, String nombre, String direccion, String telefono) {
this.id = id;
setNombre(nombre);
setDireccion(direccion);
setTelefono(telefono);
}
public int getId() {
return id;
}
public String getNombre() {
return nombre;
}
public String getDireccion() {
return direccion;
}
public String getTelefono() {
return telefono;
}
//Los setter se encargan de ajusta los string al tamaño exacto
public void setId(int id) {
this.id = id;
}
public void setNombre(String nombre) {
this.nombre = ajustarLongitud(nombre, LONGITUD_NOMBRE);
}
public void setDireccion(String direccion) {
this.direccion = ajustarLongitud(direccion, LONGITUD_DIRECCION);
}
public void setTelefono(String telefono) {
this.telefono = ajustarLongitud(telefono, LONGITUD_TELEFONO);
}
//ajusta la longitud de la cadena a la longitud deseada
public static String ajustarLongitud(String cadena, int longitud) {
if (cadena.length() >= longitud) {
// Si la longitud de la cadena es mayor o igual a la longitud deseada, se recorta
return cadena.substring(0, longitud);
} else {
// Si la longitud de la cadena es menor que la longitud deseada, se rellena con espacios
StringBuilder sb = new StringBuilder(cadena);
while (sb.length() < longitud) {
sb.append(' ');
}
return sb.toString();
}
}
@Override
public String toString() {
return id + " " + nombre + " " + direccion + " " + telefono;
}
// Método para escribir el objeto Contacto en un RandomAccessFile en la posición actual
public static void escribirContacto(RandomAccessFile raf,Contacto contacto) throws IOException {
raf.writeInt(contacto.getId());
//writeUTF escribe en el fichero el tamaño del string al inicio del string
raf.writeUTF(contacto.getNombre());
raf.writeUTF(contacto.getDireccion());
raf.writeUTF(contacto.getTelefono());
}
//Escribe un contacto en la posisión indicada
public static void escribirContacto(RandomAccessFile raf,Contacto contacto,int posicion) throws IOException {
//Posicionamos el cursor en la posicion indicada
if(raf.length() > (posicion*SIZE_REGISTRO)){
raf.seek(posicion*SIZE_REGISTRO);
escribirContacto(raf,contacto);
}else throw new IOException("Posicion fuera de rango");
}
// Método para leer el objeto Contacto desde un RandomAccessFile en la posición actual
public static Contacto leerContacto(RandomAccessFile raf) throws IOException {
int id = raf.readInt();
//el metodo readUTF lee el string de tamaño indicado al inicio
String nombre = raf.readUTF();
String direccion = raf.readUTF();
String telefono = raf.readUTF();
return new Contacto(id, nombre, direccion, telefono);
}
//Leer un contacto posicionando el cursor en la posicion indicada
public static Contacto leerContacto(RandomAccessFile raf, int posicion) throws IOException {
//Posicionamos el cursor en la posición indicada
if(raf.length() > (posicion*SIZE_REGISTRO)){
raf.seek(posicion*SIZE_REGISTRO);
return leerContacto(raf);
}else throw new IOException("Posicion fuera de rango");
}
}
Un ejemplo de uso de la clase anterior
public static void main(String[] args) {
Contacto[] contactos = {
new Contacto(1, "Juan", "Calle 123, Alicante", "123456789"),
new Contacto(2, "Maria", "Carrera 456", "987654321"),
new Contacto(3, "Pedro", "Avenida XYZ", "456123789")
};
try(RandomAccessFile raf = new RandomAccessFile("agenda.dat", "rw");) {
//escritura secuencial
for (Contacto contacto : contactos) {
Contacto.escribirContacto(raf,contacto);
}
System.out.println("Contactos guardados en agenda.dat");
//lectura secuencial del fichero
raf.seek(0);
while (raf.getFilePointer() < raf.length()) {
Contacto contacto = Contacto.leerContacto(raf);
System.out.println(contacto);
System.out.println(raf.getFilePointer());
}
//lectura aleatoria del fichero
Contacto contacto = Contacto.leerContacto(raf, 1);
System.out.println("Contacto aleatorio: " + contacto);
//escritura aleatoria del fichero
contacto.setNombre("Maria Jose");
contacto.setTelefono("555676766");
Contacto.escribirContacto(raf, contacto, 1);
//leer el anterior
contacto = Contacto.leerContacto(raf, 1);
System.out.println("Contacto aleatorio: " + contacto);
} catch (IOException e) {
e.printStackTrace();
}
}