Saltar a contenido

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.

JavaIO

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);
    }
}
Si el programa requiere trabajar con objetos, todos los objetos tienen que ser del mismo tamaño. Vamos a ver un ejemplo en el que fijamos el tamaño de los string en el que recortamos el los campos o rellenamos con espacios si no tiene la longitud definida y calculamos el tamaño que tiene un objeto de tipo Contacto

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();
    }
}