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.
- r: el fichero es de solo lectura.
- rw: se abre en modo lectura-escritura.
- rws: se abre para lectura y escritura y cada cambio en los datos del fichero se escribirá inmediatamente en el dispositivo físico.
> RandomAccessFile raf = new RandomAccessFile("myfile.dat", "rw");
Ejemplo de un programa que añade texto al final de un fichero¶
public static void main(String[] args) {
RandomAccessFile file = null;
try {
file = new RandomAccessFile("file.txt", "rw");
file.seek(file.length()); // Moving file pointer to the end.
file.writeBytes("\nJava"); // Append text.
file.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
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();
}
}