Curso de Java. Entrada y salida de ficheros

Escrito por Adrián Crespo
Java
0

Continuamos con la entrada y salida de ficheros en el curso de Java de RedesZone.net. Para los que no os acordéis, en la entrega anterior, hablamos sobre todo lo referido a la clase Scanner de Java, que nos facilitaba la lectura de ficheros de texto que siguen un determinado patrón.

En la entrega de hoy, que será la última entrega relacionada con entrada y salida, hablaremos sobre cómo utilizar los ficheros como tablas, es decir, si tenemos la necesidad, cómo es posible acceder a cualquier posición del fichero como si de una tabla se tratase.

Hasta ahora todos los ficheros se han manejado mediante clases que representan secuencias

En muchas ocasiones es conveniente poder acceder a los datos en cualquier orden, como en una tabla

  • Las diferencias con un array son:
  1. Tamaño no limitado a priori
  2. Memoria persistente (no volátil)

Para ello Java dispone de la clase RandomAccessFile

  • Permite acceso aleatorio al fichero
  • No es un Stream
  • Se puede usar para leer, o leer y escribir a la vez

En java son ficheros que contienen bytes

Se numeran con un índice que empieza en cero.

Existe un índice almacenado en el sistema que se llama el puntero de lectura/escritura

  • Las lecturas y escrituras se hacen a partir de él, en secuencia
  • Es posible cambiar el puntero de lectura/escritura

Si escribimos pasado el final del fichero, su tamaño se amplía

Operaciones más habituales


Ejemplo con tabla de datos

import java.io.*;

/**

* Clase que contiene los datos de un libro

*/

public class Libro {

// constantes estáticas

public static final int maxCaracteresTítulo=100;

public static final int tamañoEnBytes =

maxCaracteresTitulo+1 // título + n

+Integer.SIZE/8 // publicado

+Double.SIZE/8 // precio

+1; // tamaño par

// atributos privados

private String título;

private int publicado; // año de publicación

private double precio; // en euros

/**

* Constructor al que se le pasan los datos del

* libro

*/

public Libro(String titulo, int publicado,

double precio) {

// asegurarse de que el titulo no supera

// maxCaracteresTítulo

if (titulo.length()>maxCaracteresTítulo) {

this.titulo=

titulo.substring(0,maxCaracteresTítulo);

} else {

this.titulo=titulo;

}

this.publicado=publicado;

this.precio=precio;

}

/**

* Lee de fichero

*/

public static Libro leeDeFichero(

RandomAccessFile fich) throws IOException {

// lee los tres datos, por orden

int publi=fich.readInt();

double prec=fich.readDouble();

String tit=fich.readLine().trim();

// crea y retorna el libro

return new Libro(tit,publi,prec);

}
 /**
 * Escribe en el fichero
 */
public void escribeEnFichero(
 RandomAccessFile fich) throws IOException {
 // escribe los tres datos, por orden
 fich.writeInt(publicado);
 fich.writeDouble(precio);
 fich.writeBytes(titulo+'n');
 }
 métodos observadores, toString, ...
} // clase Libro

 


import java.io.*;

/**
 * Tabla de libros persistente almacenada en
 * un fichero de acceso aleatorio
 */
public class TablaLibros {
 // atributos privados
private RandomAccessFile fich;
 /**
 * Constructor al que se le pasa el nombre
 * del fichero
 */
 public TablaLibros(String nombreFichero)
 throws FileNotFoundException {
 fich = new RandomAccessFile(nombreFichero,"rw");
 }

/**
 * Obtener el elemento de la tabla que esta en
 * "índice"
 */
 public Libro obten(int índice)
 throws IOException {
 // posiciona el contador de lectura/escritura
long pos=indice*Libro.tamañoEnBytes;
 fich.seek(pos);

 // lee y retorna el libro
return Libro.leeDeFichero(fich);
 }

/** Escribir un libro en la posición "índice"
 * de la tabla */
public void almacena(int índice, Libro l)
 throws IOException {
 // posiciona el contador de lectura/escritura
long pos=indice*Libro.tamañoEnBytes;
 fich.seek(pos);

 // escribe el libro
 l.escribeEnFichero(fich);
 }
 /** Cerrar la tabla */
public void cerrar() throws IOException {
 fich.close();
 }
} // clase TablaLibros

 


// ejemplo de uso de TablaLibros
TablaLibros t = null;

try {
 t = new TablaLibros("random.dat");

 Libro libro1 = new Libro("Java", 2006, 15.0);
 Libro libro2 = new Libro("1984", 1949, 25.0);

 t.almacena(0,libro1);
 t.almacena(1,libro2);
 Libro l1= t.obten(0);
Libro l2= t.obten(1);
} finally {
 if (t != null) {
 t.cerrar();
 }
}

Para escribir objetos en ficheros de acceso aleatorio es preciso convertirlos a un array de bytes

  • A este proceso se le llama “serialización”

Para leer el objeto hay que “deserializarlo”

La clase ByteArrayOutputStream nos ayuda en este proceso

Tener cuidado pues la serialización incluye objetos a los que se hace referencia

  • Esto puede hacer que el tamaño del objeto serializado varíe mucho
  • Para acceso aleatorio es conveniente tener tamaños fijos

Ejemplo de serialización


// serializa un libro y le escribe en un fichero
RandomAccessFile fich = null;
try {
 // abre el fichero de acceso aleatorio
 fich = new RandomAccessFile (nomFich, "rw");
 // pone el puntero al principio
 fich.seek(0L);
// serializa el libro convirtiéndolo a una
 // secuencia de bytes
ByteArrayOutputStream bos =
 new ByteArrayOutputStream();
 ObjectOutputStream out =
 new ObjectOutputStream(bos);
 out.writeObject(libro);
 out.close();
// obtiene los bytes del libro serializado
 byte[] buf = bos.toByteArray();
// escribe los bytes en el fichero
 fich.write(buf);
} finally {
 if (fich!=null) {
 fich.close();
 }
}

Ejemplo de deserialización

// recupera del fichero un libro serializado
RandomAccessFile fich = null;
try {
 // abre el fichero de acceso aleatorio
 fich = new RandomAccessFile (nomFich, "r");

 // pone el puntero al principio
 fich.seek(0L);
// Lee un array de bytes del fichero
 byte[] bytes = new byte[(int) fich.length()];
 fich.readFully(bytes);

// Deserializa el array de bytes
 ObjectInputStream in = new ObjectInputStream(
 new ByteArrayInputStream(bytes));
 libro=(Libro) in.readObject();
 in.close();

} finally {
 if (fich!=null) {
 fich.close();
 }
}