Vous êtes sur la page 1sur 25

1. Flujos.

Los programas Java realizan las E/S a través de flujos. Un flujo es una
abstracción que consume o produce información. Todos los flujos se comportan de la
misma manera, aun cuando los dispositivos físicos reales a los que están vinculados
difieren entre si. Por lo tanto se pueden aplicar las mismas clases de E/S a cualquier tipo
de dispositivo. Java implementa flujos dentro de las jerarquías de clases que se definen
en el paquete java.io.

Java define dos tipos de flujos: de bytes y de caracteres. Los flujos de bytes
suponen un medio cómodo para gestionar la entrada y salida de bytes. Los flujos de
caracteres hacen lo propio con los caracteres y utilizan el código Unicote, y por lo tanto
se pueden internacionalizar. En algunos casos los flujos de caracteres son más eficientes
que los flujos de bytes.

Las clases de flujos de bytes.

Los flujos de bytes se definen a través de dos jerarquías de clases. En el nivel


superior se encuentran dos clases abstractas, InputStream y OutputStream. Cada una de
estas clases tiene muchas subclases concretas que gestionan las diferencias entre los
distintos dispositivos, como archivos de disco, conexiones de red, e incluso búfer de
memoria.

Clases de flujo de bytes:

Clases de flujo Significado


BufferedInputStream Flujo de entrada con búfer.
BufferedOutputStream Flujo de salida con búfer.
ByteArrayInputStream Flujo de entrada que lee de una matriz de
bytes.
ByteArrayOutputStream Flujo de salida que escribe en una matriz
de bytes.
DataInputStream Flujo de entrada que tiene los métodos
para leer los tipos de datos básicos de
Java.
DataOutputStream Flujo de salida que tiene los métodos para
escribir los tipos de datos básicos de Java.
FileInputStream Flujo de entrada que lee de un fichero.
FileOutputStream Flujo de salida que escribe en un fichero.
FilterInputStream Implementa InputStream.
FilterOutputStream Implementa OutputStream.
InputStream Clase abstracta que describe la entrada del
flujo.
ObjectInputStream Flujo de entrada para objetos.
ObjectOutputStream Flujo de salida para objetos.
PipedInputStream Canal de entrada.
PipedOutputStream Canal de salida.
PrintStream Flujo de salida que contiene print() y
println().
PushbackInputStream Flujo de entrada que permite devolver un
byte que se ha leído al nuevo flujo de
entrada.
RandomAccessFile Permite acceso aleatorio a un fichero de
E/S.
SequenceInputStream Flujo de entrada que es una combinación
de dos flujos más de entrada que se leen
de forma secuencial, uno después del otro.

Las clases abstractas InputStream y OutputStream definen muchos métodos


clave que implementan las otras clases de flujos. Dos de los más importantes son los
métodos read() y write(), que se encargan de leer y escribir bytes de datos,
respectivamente. Ambos métodos se declaran como abstractos dentro de InputStream y
OutputStream, y las clases derivadas de flujos los sobrescriben.

Las clases de flujos de caracteres.

Los flujos de caracteres se definen a través de dos jerarquías de clases. En el


nivel superior se encuentran las clases abstractas Reader y Writer que gestrionan el flujo
de caracteres Unicote. Java dispone de varias subclases concretas para cada una de estas
dos clases:

Clases de flujo Significado


BufferedReader Flujo de entrada de caracteres por búfer.
BufferedWriter Flujo de salida de caracteres por búfer.
CharArrayReader Flujo de entrada que lee de una matriz de
caracteres.
CharArrayWriter Flujo de salida que escribe en una matriz
de caracteres.
FileReader Flujo de entrada que lee de un fichero.
FileWriter Flujo de salida que escribe en un fichero.
FilterReader Lector con filtro.
FilterWriter Escritor con filtro.
InputStreamReader Flujo de entrada que traslada bytes a
caracteres.
LineNumberReader Flujo de entrada que cuenta las líneas.
OutputStreamWriter Flujo de salida que traslada caracteres a
bytes.
PipedReader Canal de entrada.
PipedWriter Canal de salida.
PrintWriter Flujo de salida que contiene print() y
println().
PushbackReader Flujo de entrada que permite que los
caracteres vuelvan al flujo de entrada.
Reader Clase abstracta que describe la entrada de
flujo de caracteres.
StringReader Flujo de entrada que lee de una cadena.
Writer Clase abstracta que describe la salida de
flujo de caracteres.

2. Organigrama de flujo y fichero.

En el lenguaje Java los flujos de datos se describen mediante clases que forman
jerarquías según sea el tipo de dato char Unicode de 16 bits o byte de 8 bits. A su vez,
las clases se agrupan en jerarquías según sea su función de lectura o de escritura. La
característica de internacionalización del lenguaje Java es la razón por la que existe una
jerarquía separada de clases para la lectura y escritura de caracteres. Todas estas clases
se encuentran en el paquete java.io.

Reader y Writer son las clases bases de la jerarquía para los flujos de caracteres:
Para leer o escribir datos binarios tales como imágenes o sonidos, se emplea otra
jerarquía de clases cuyas clases base son InputStream y OutputStream:

Lectura.

Las clases Reader e InputStream son similares aunque se refieren a distintos tipos de
datos, lo mismo ocurre con Writer y OutputSream. Por ejemplo, Reader proporciona
tres métodos para leer un carácter char o un array de caracteres:

int read()
int read(char buf[])
int read(char buf[], int offset, int len)

InputStream proporciona métodos similares para leer un byte o un array de bytes:

int read()
int read(byte buf[])
int read(byte buf[], int offset, int len)

La primera versión lee un byte como entero del flujo de entrada, devuelve –1 si no hay
más datos que leer. La segunda versión, lee un array de bytes devolviendo el número de
bytes leídos. La tercera versión, lee también, un array de bytes, pero nos permite
especificar, la posición de comienzo del array en la que se empiezan a guardar los bytes,
y el máximo número de bytes que se van a leer.

Escritura.
La clase Writer proporciona tres métodos para escribir un carácter char o un array de
caracteres:

int write(int c)
int write(char buf[])
int write(char buf[], int offset, int len)

La clase OutputStream proporciona métodos similares:

int write(int c)
int write(byte buf[])
int write(byte buf[], int offset, int len)

Flujos de entrada.

Objetos FileInputStream.

Los objetos FileInputStream típicamente representan ficheros de texto accedidos


en orden secuencial, byte a byte. Con FileInputStream, se puede elegir acceder a un
byte, varios bytes o al fichero completo.

Apertura de un FileInputStream.

Para abrir un FileInputStream sobre un fichero, se le da al constructor un String


o un objeto File:

FileInputStream miFicheroSt;
miFicheroSt = new FileInputStream( "/etc/kk" );

También se puede utilizar:

File miFichero FileInputStream miFicheroSt;


miFichero = new File( "/etc/kk" );
miFicheroSt = new FileInputStream(
miFichero );

Lectura de un FileInputStream.

Una vez abierto el FileInputStream, se puede leer de él. El método read() tiene
muchas opciones:

Lee un byte y devuelve -1 al final del flujo:


int read();

Llena todo el array, si es posible. Devuelve el número de bytes leídos o -1 si se alcanzó


el final del flujo:
int read( byte b[] );
Lee longitud bytes en b comenzando por b[offset]. Devuelve el número de bytes
leídos o -1 si se alcanzó el final del stream.
int read( byte b[],int offset,int longitud );

Cierre de FileInputStream.

Cuando se termina con un fichero, existen dos opciones para cerrarlo:


explícitamente, o implícitamente cuando se recicla el objeto (el garbage collector se
encarga de ello).
Para cerrarlo explícitamente, se utiliza el método close():
miFicheroSt.close();

Ejemplo: Visualización de un fichero.


Si la configuración de la seguridad de Java permite el acceso a ficheros, se puede
ver el contenido de un fichero en un objeto TextArea. El código siguiente contiene los
elementos necesarios para mostrar un fichero:

FileInputStream fis;
TextArea ta;

public void init() {


byte b[] = new byte[1024];
int i;

// El buffer de lectura se debe hacer lo suficientemente


// grande o esperar a saber el tamaño del fichero
String s;

try {
fis = new FileInputStream( "/etc/kk" );
} catch( FileNotFoundException e ) {
/* Hacer algo */
}

try {
i = fis.read( b );
} catch( IOException e ) {
/* Hacer algo */
}

s = new String( b,0 );


ta = new TextArea( s,5,40 );
add( ta );
}

Objetos DataInputStream.
Los objetos DataInputStream se comportan como los FileInputStream. Los
flujos de datos pueden leer cualquiera de las variables de tipo nativo, como floats, ints o
chars. Generalmente se utilizan DataInputStream con ficheros binarios.

Apertura y cierre de DataInputStream.

Para abrir y cerrar un objeto DataInputStream, se utilizan los mismos métodos


que para FileInputStream:

DataInputStream miDStream;
FileInputStream miFStream;

// Obtiene un controlador de fichero


miFStream = new FileInputStream "/etc/ejemplo.dbf" );
//Encadena un fichero de entrada de datos
miDStream = new DataInputStream( miFStream );

// Ahora se pueden utilizar los dos flujos de entrada para


// acceder al fichero (si se quiere...)
miFStream.read( b );
i = miDStream.readInt();

// Cierra el fichero de datos explícitamente


// Siempre se cierra primero el fichero flujo de mayor nivel
miDStream.close();
miFStream.close();

Lectura de un DataInputStream.

Al acceder a un fichero como DataInputStream, se pueden utilizar los mismos


métodos read() de los objetos FileInputStream. No obstante, también se tiene acceso a
otros métodos diseñados para leer cada uno de los tipos de datos:
byte readByte()
int readUnsignedByte()
short readShort()
int readUnsignedShort()
char readChar()
int readInt()
long readLong()
float readFloat()
double readDouble()
String readLine()

Cada método leerá un objeto del tipo pedido.


Para el método String readLine(), se marca el final de la cadena con \n, \r, \r\n o con
EOF.
Para leer un long, por ejemplo:
long numeroSerie;
...
numeroSerie = miDStream.readLong();
Flujos de entrada de URLs.

Además del acceso a ficheros, Java proporciona la posibilidad de acceder a


URLs como una forma de acceder a objetos a través de la red. Se utiliza implícitamente
un objeto URL al acceder a sonidos e imágenes, con el método getDocumentBase() en
los applets:

String imagenFich = new String( "imagenes/pepe.gif" );


imagenes[0] = getImage( getDocumentBase(),imagenFich );

No obstante, se puede proporcionar directamente un URL, si se quiere:

URL imagenSrc;
imagenSrc = new URL( "http://enterprise.com/~info" );
imagenes[0] = getImage( imagenSrc,"imagenes/pepe.gif" );

Apertura de un flujo de entrada de URL.

También se puede abrir un flujo de entrada a partir de un URL. Por ejemplo, se


puede utilizar un fichero de datos para un applet:

ImputStream is;
byte buffer[] = new byte[24];
is = new URL( getDocumentBase(),datos).openStream();

Ahora se puede utilizar is para leer información de la misma forma que se hace con un
objeto FileInputStream:

is.read( buffer,0,buffer.length );

Flujos de salida.

Objetos FileOutputStream.

Los objetos FileOutputStream son útiles para la escritura de ficheros de texto.


Como con los ficheros de entrada, primero se necesita abrir el fichero para luego
escribir en él.

Apertura de un FileOutputStream.

Para abrir un objeto FileOutputStream, se tienen las mismas posibilidades que


para abrir un fichero flujo de entrada. Se le da al constructor un String o un objeto File.
FileOutputStream miFicheroSt;
miFicheroSt = new FileOutputStream( "/etc/kk" );

Como con los flujos de entrada, también se puede utilizar:

File miFichero FileOutputStream miFicheroSt;

miFichero = new File( "/etc/kk" );


miFicheroSt = new FileOutputStream( miFichero );

Escritura en un FileOutputStream.

Una vez abierto el fichero, se pueden escribir bytes de datos utilizando el


método write(). Como con el método read() de los flujo de entrada, tenemos tres
posibilidades:

Escribe un byte:
void write( int b );

Escribe todo el array, si es posible.


void write( byte b[] );

Escribe longitud bytes en b comenzando por b[offset].


void write( byte b[],int offset,int longitud );

Cierre de FileOutputStream.

Cerrar un flujo de salida es similar a cerrar streams de entrada. Se puede utilizar


el método explícito:
miFicheroSt.close();

O, se puede dejar que el sistema cierre el fichero cuando se recicle miFicheroSt.

El programa de ejemplo: Telefonos.java, pregunta al usuario una lista de


nombres y números de teléfono. Cada nombre y número se añade a un fichero situado
en una localización fija. Para indicar que se ha introducido toda la lista, el usuario
especifica "Fin" ante la solicitud de entrada del nombre. Una vez que el usuario ha
terminado de teclear la lista, el programa creará un fichero de salida que se mostrará en
pantalla o se imprimirá.

Por ejemplo:
95-4751232,Juanito
564878,Luisa
123456,Pepe
347698,Antonio
91-3547621,Maria

El código fuente del programa es el siguiente:


import java.io.*;

class Telefonos {
static FileOutputStream fos;
public static final int longLinea = 81;

public static void main( String args[] ) throws IOException {


byte tfno[] = new byte[longLinea];
byte nombre[] = new byte[longLinea];

fos = new FileOutputStream( "telefono.dat" );


while( true )
{
System.err.println( "Teclee un nombre ('Fin' termina)" );
leeLinea( nombre );
if( "fin".equalsIgnoreCase( new String( nombre,0,0,3 ) ) )
break;

System.err.println( "Teclee el numero de telefono" );


leeLinea( tfno );
for( int i=0; tfno[i] != 0; i++ )
fos.write( tfno[i] );
fos.write( ',' );
for( int i=0; nombre[i] != 0; i++ )
fos.write( nombre[i] );
fos.write( '\n' );
}
fos.close();
}

private static void leeLinea( byte linea[] ) throws IOException {


int b = 0;
int i = 0;

while( (i < ( longLinea-1) ) &&


( ( b = System.in.read() ) != '\n' ) )
linea[i++] = (byte)b;
linea[i] = (byte)0;
}
}

Flujos de salida con buffer.

Si se trabaja con gran cantidad de datos, o se escriben muchos elementos


pequeños, será una buena idea utilizar un flujo de salida con buffer. Los flujos con
buffer ofrecen los mismos métodos de la clase FileOutputStream, pero toda salida se
almacena en un buffer. Cuando se llena el buffer, se envía a disco con una única
operación de escritura; o, en caso necesario, se puede enviar el buffer a disco en
cualquier momento.

Creación de flujos de salida con buffer.


Para crear un flujo BufferedOutput, primero se necesita un flujo FileOutput
normal; entonces se le añade un buffer al flujo:

FileOutputStream miFileStream;
BufferdOutpurStream miBufferStream;
// Obtiene un controlador de fichero
miFileStream = new FileOutputStream( "/tmp/kk" );
// Encadena un stream de salida con buffer
miBufferStream = new BufferedOutputStream( miFileStream );

Volcado y cierre de flujos de salida con buffer.

Al contrario que los flujos FileOutput, cada escritura al buffer no se corresponde


con una escritura en disco. A menos que se llene el buffer antes de que termine el
programa, cuando se quiera volcar el buffer explícitamente se debe hacer mediante una
llamada a flush():

// Se fuerza el volcado del buffer a disco


miBufferStream.flush();
// Cerramos el fichero de datos. Siempre se ha de cerrar primero
// el fichero flujo de mayor nivel
miBufferStream.close();
miFileStream.close();

Streams DataOutput.

Java también implementa una clase de salida complementaria a la clase


DataInputStream. Con la clase DataOutputStream, se pueden escribir datos binarios
en un fichero.

Apertura y cierre de objetos DataOutputStream.

Para abrir y cerrar objetos DataOutputStream, se utilizan los mismos métodos


que para los objetos FileOutputStream:

DataOutputStream miDataStream;
FileOutputStream miFileStream;
BufferedOutputStream miBufferStream;

// Obtiene un controlador de fichero


miFileStream = new FileOutputStream( "/tmp/kk" );
// Encadena un stream de salida con buffer (por eficiencia)
miBufferStream = new BufferedOutputStream( miFileStream );
// Encadena un fichero de salida de datos
miDataStream = new DataOutputStream( miBufferStream );

// Ahora se pueden utilizar los dos flujos de entrada para


// acceder al fichero (si se quiere)
miBufferStream.write( b );
miDataStream.writeInt( i );

// Cierra el fichero de datos explícitamente. Siempre se cierra


// primero el fichero flujo de mayor nivel
miDataStream.close();
miBufferStream.close();
miFileStream.close();

Escritura en un objeto DataOutputStream.

Cada uno de los métodos write() accesibles por los FileOutputStream también lo
son a través de los DataOutputStream. También encontrará métodos complementarios a
los de DataInputStream:

void writeBoolean( boolean b );


void writeByte( int i );
void writeShort( int i );
void writeChar( int i );
void writeInt( int i );
void writeFloat( float f );
void writeDouble( double d );
void writeBytes( String s );
void writeChars( string s );

Para las cadenas, se tienen dos posibilidades: bytes y caracteres. Hay que recordar que
los bytes son objetos de 8 bits y los caracteres lo son de 16 bits. Si nuestras cadenas
utilizan caracteres Unicode, debemos escribirlas con writeChars().

Contabilidad de la salida.

Otra función necesaria durante la salida es el método size(). Este método


simplemente devuelve el número total de bytes escritos en el fichero. Se puede utilizar
size() para ajustar el tamaño de un fichero a múltiplo de cuatro. Por ejemplo, de la
forma siguiente:

. . .
int numBytes = miDataStream.size() % 4;
for( int i=0; i < numBytes; i++ )
miDataStream.write( 0 );
. . .
3. Flujos de tipo objeto.

La serialización de objetos de Java permite tomar cualquier objeto que


implemente la interfaz Serializable y convertirlo en una secuencia de bits que puede ser
posteriormente restaurada para regenerar cualquier objeto original. Esto es cierto
incluso a través de una red, lo que significa que el mecanismo de serialización
compensa automáticamente las diferencias entre sistemas operativos. Es decir, se puede
crear un objeto en una máquina Windows, serializarlo, y enviarlo a través de la red a
una máquina Unix, donde será reconstruido correctamente. No hay que preocuparse por
las diferentes representaciones de datos en las distintas máquinas, al igual no importa la
ordenación de los bytes y el resto de detalles.

La serialización es una característica añadida al lenguaje Java para dar soporte a:


- La invocación remota de objetos (RMI, Remote Method Invocation)
- La persistencia

La invocación remota de objetos permite a los objetos que viven en otros


ordenadores comportarse como si vivieran en nuestra propia máquina. La serialización
es necesaria para transportar los argumentos y los valores de retorno.
La persistencia, es una característica importante de los JavaBeans. El estado de
un componente es configurado durante el diseño. La serialización nos permite guardar
el estado de un componente en disco, abandonar el Entorno Integrado de Desarrollo
(IDE) y restaurar el estado de dicho componente cuando se vuelve a correr el IDE.

La interfaz Serializable.

Un objeto se puede serializar si implementa la interfaz Serializable. Esta interfaz


no declara ninguna función miembro, se trata de una interfaz vacía:

import java.io.*;
public interface Serializable{
}

Para hacer una clase serializable simplemente hay que implementar la interfaz
Serializable:

public class Lista implements java.io.Serializable{


private int[] x;
private int n;
//otros miembros...
}

No tenemos que escribir ningún otro método. El método defaultWriteObject de


la clase ObjectOutputStream realiza la serialización de los objetos de una clase. Este
método escribe en el flujo de salida todo lo necesario para reconstruir dichos objetos:
1. La clase del objeto
2. La firma de la clase (class signature)
3. Los valores de los miembros que no tengan los modificadores static o
transient, incluyendo los miembros que se refieren a otros objetos.
El método defaultReadObject de la clase ObjectInputStream realiza la
deserialización de los objetos de una clase. Este método lee el flujo de entrada y
reconstruye los objetos de dicha clase.

Lectura/Escritura.

Los flujos de datos ObjectInputStream y ObjectOutputStream están


especializados en la lectura y escritura de objetos. El comportamiento de estos dos
flujos es similar a sus correspondientes que procesan flujos de datos primitivos
DataInputStream y DataOutputStream.

Escribir objetos al flujo de salida ObjectOutputStream requiere los siguientes


pasos:

1. Creamos un objeto de la clase Lista:

Lista lista1= new Lista(new int[]{12, 15, 11, 4, 32});

2. Creamos un flujo de salida de disco, pasándole el nombre del archivo en disco o


un objeto de la clase File:

FileOutputStream fileOut=new FileOutputStream("media.obj");

3. El flujo de salida ObjectOutputStream es el que procesa los datos y se ha de


vincular a un objeto fileOut de la clase FileOutputStream:

ObjectOutputStream salida=new ObjectOutputStream(fileOut);

o en una sola línea:

ObjectOutputStream salida=new ObjectOutputStream(


new FileOutputStream("media.obj"));

4. El método writeObject escribe los objetos al flujo de salida y los guarda en un


archivo en disco. Por ejemplo, un string y un objeto de la clase Lista:

salida.writeObject("guardar este string y un objeto\n");


salida.writeObject(lista1);

5. Finalmente, se cierran los flujos:

salida.close();
El proceso de lectura es paralelo al proceso de escritura, por lo que leer objetos del
flujo de entrada ObjectInputStream es muy simple y requiere los siguientes pasos.

1. Creamos un flujo de entrada a disco, pasándole el nombre del archivo en disco o


un objeto de la clase File:

FileInputStream fileIn=new FileInputStream("media.obj");

2. El flujo de entrada ObjectInputStream es el que procesa los datos y se ha de


vincular a un objeto fileIn de la clase FileInputStream.

ObjectInputStream entrada=new ObjectInputStream(fileIn);

o en una sola línea:

ObjectInputStream entrada=new ObjectInputStream(


new FileInputStream("media.obj"));

3. El método readObject lee los objetos del flujo de entrada, en el mismo orden en
el que ha sido escritos. Primero un string y luego, un objeto de la clase Lista.

String str=(String)entrada.readObject();
Lista obj1=(Lista)entrada.readObject();

4. Se realizan tareas con dichos objetos, por ejemplo, desde el objeto obj1 de la
clase Lista se llama a la función miembro valorMedio, para hallar el valor medio
del array de datos, o se muestran en la pantalla:

System.out.println("Valor medio "+obj1.valorMedio());


System.out.println("-----------------------------");
System.out.println(str+obj1);

5. Finalmente, se cierra los flujos:

entrada.close();

Cuando un miembro dato de una clase contiene información sensible, hay


disponibles varias técnicas para protegerla. Incluso cuando dicha información es privada
(el miembro dato tiene el modificador private) una vez que se ha enviado al flujo de
salida alguien puede leerla en el archivo en disco o interceptarla en la red.
El modo más simple de proteger la información sensible, como una contraseña
(password) es la de poner el modificador transient delante del miembro dato que la
guarda.
La clase Cliente tiene dos miembros dato, el nombre del cliente y la contraseña o
password. Redefine la función toString miembro de la clase base Object. Esta función
devolverá el nombre del cliente y la contraseña. En el caso de que el miembro password
guarde el valor null se imprimirá el texto (no disponible).

En el cuadro que sigue se muestra el código que define la clase Cliente:

public class Cliente implements java.io.Serializable{


private String nombre;
private transient String passWord;
public Cliente(String nombre, String pw) {
this.nombre=nombre;
passWord=pw;
}
public String toString(){
String texto=(passWord==null) ? "(no disponible)" : passWord;
texto+=nombre;
return texto;
}
}

A continuación se muestra los pasos para guardar un objeto de la clase Cliente en el


archivo cliente.obj. Posteriormente, se lee el archivo para reconstruir el objeto obj1 de
dicha clase.

1. Se crea el objeto cliente de la clase Cliente pasándole el nombre del cliente


"Angel" y la contraseña "xyz".
2. Se crea un flujo de salida (objeto salida de la clase ObjectOutputStream) y se
asocia con un objeto de la clase FileOutputStream para guardar la información
en el archivo cliente.obj.
3. Se escribe el objeto cliente en el flujo de salida mediante writeObject.
4. Se cierra el flujo de salida llamando a close.

Cliente cliente=new Cliente("Angel", "xyz");

ObjectOutputStream salida=new ObjectOutputStream(new

FileOutputStream("cliente.obj"));

salida.writeObject("Datos del cliente\n");


salida.writeObject(cliente);
salida.close();

Para reconstruir el objeto obj1 de la clase Cliente se procede del siguiente modo:

1. Se crea un flujo de entrada (objeto entrada de la clase ObjectInputStream) y se


asocia con un objeto de la clase FileInputStream para leer la información que
guarda el archivo cliente.obj.
2. Se lee el objeto cliente en el flujo de salida mediante readObject.
3. Se imprime en la pantalla dicho objeto llamando implícitamente a su función
miembro toString.
4. Se cierra el flujo de entrada llamando a close.

ObjectInputStream entrada=new ObjectInputStream(new

FileInputStream("cliente.obj"));

String str=(String)entrada.readObject();
Cliente obj1=(Cliente)entrada.readObject();

System.out.println("------------------------------");
System.out.println(str+obj1);
System.out.println("------------------------------");

entrada.close();

La salida del programa es:

Datos del cliente


(no disponible) Angel

La información sensible guardada en el miembro dato password que tiene por


modificador transient no ha sido guardada en el archivo. En la reconstrucción del
objeto obj1 con la información guardada en el archivo el miembro dato password toma
el valor null.

Objetos Compuestos.

Se tiene la clase Rectángulo que contiene un subobjeto de la clase Punto. A


dichas clases se les añade la redefinición de la función toString miembro de la clase
base Object (esta redefinición no es necesaria aunque es ilustrativa para explicar el
comportamiento de un objeto compuesto). Ambas clases implementan la interfaz
Serializable.

public class Punto implements java.io.Serializable{


private int x;
private int y;
//otros miembros...

public String toString(){


return new String("("+x+", "+y+")");
}
}

public class Rectangulo implements java.io.Serializable{


private int ancho ;
private int alto ;
private Punto origen;
//otras funciones miembro...

public String toString(){


String texto=origen+" w:"+ancho+" h:"+alto;
return texto;
}
}
En la definición de toString de la clase Rectangulo se hace una llamada implícita
a la función toString miembro de la clase Punto. La composición permite reutilizar el
código existente.
Para guardar en un archivo un objeto de la clase Rectangulo hay que seguir los
mismos pasos que para guardar un objeto de la clase Lista o de la clase Cliente.

Rectangulo rect=new Rectangulo(new Punto(10,10), 30, 60);

ObjectOutputStream salida=new ObjectOutputStream(new

FileOutputStream("figura.obj"));

salida.writeObject("guardar un objeto compuesto\n");


salida.writeObject(rect);
salida.close();

Para reconstruir un objeto de la clase Rectangulo a partir de los datos guardados


en el archivo hay que seguir los mismos pasos que en los dos ejemplos previos:

ObjectInputStream entrada=new ObjectInputStream(new


FileInputStream("figura.obj"));
String str=(String)entrada.readObject();
Rectangulo obj1=(Rectangulo)entrada.readObject();

System.out.println("------------------------------");
System.out.println(str+obj1);
System.out.println("------------------------------");

Entrada.close();

En el caso de no implementar el interface Serializable en la clase Punto que


describe el subobjeto de la clase Rectangulo, se lanza una excepción, imprimiéndose en
la consola:

java.io.NotSerializableException: archivo.Punto.

La Herencia.

Se tiene una jerarquía formada por una clase base denominada Figura y dos
clases derivadas denominadas Circulo y Rectangulo. La clase base Figura implementa
la interfaz Serializable y en la clase Circulo de define una constante estática PI con una
aproximación de 4 decimales. De este modo se prueba el comportamiento de un
miembro estático en el proceso de serialización.
Para serializar objetos de una jerarquía solamente la clase base tiene que
implementar el interface Serializable:

public abstract class Figura implements java.io.Serializable{


protected int x;
protected int y;
public Figura(int x, int y) {
this.x=x;
this.y=y;
}
public abstract double area();
}

class Circulo extends Figura{


protected double radio;
private static final double PI=3.1416;
public Circulo(int x, int y, double radio){
super(x,y);
this.radio=radio;
}
public double area(){
return PI*radio*radio;
}
}

class Rectangulo extends Figura{


protected double ancho, alto;
public Rectangulo(int x, int y, double ancho, double alto){
super(x,y);
this.ancho=ancho;
this.alto=alto;
}
public double area(){
return ancho*alto;
}
}

Se serializará dos objetos uno de la clase Rectangulo y otro de la clase Circulo, y


a continuación se reconstruirá dichos objetos. Una vez de que se dispone de los objetos
se llama a las funciones area para calcular el área de cada una de las figuras.

Figura fig1=new Rectangulo(10,15, 30, 60);


Figura fig2=new Circulo(12,19, 60);

ObjectOutputStream salida=new ObjectOutputStream(new


FileOutputStream("figura.obj"));
salida.writeObject("guardar un objeto de una clase derivada\n");
salida.writeObject(fig1);
salida.writeObject(fig2);
salida.close();

fig1 y fig2 son dos referencias de la clase base Figura en la que se guardan objetos de
las clases derivadas Rectangulo y Circulo, respectivamente.

Para leer los datos guardados en el archivo figura.obj y reconstruir dos objetos obj1 y
obj2 de las clases Rectangulo y Circulo respectivamente, se procede de forma similar a
la estudiada en los apartados previos:

ObjectInputStream entrada=new ObjectInputStream(new


FileInputStream("figura.obj"));
String str=(String)entrada.readObject();
Figura obj1=(Figura)entrada.readObject();
Figura obj2=(Figura)entrada.readObject();
System.out.println("------------------------------");
System.out.println(obj1.getClass().getName()+
" origen ("+obj1.x+", "+obj1.y+")"+" area="+obj1.area());
System.out.println(obj2.getClass().getName()+
" origen ("+obj2.x+", "+obj2.y+")"+" area="+obj2.area());
System.out.println("------------------------------");
entrada.close();

Se puede observar que obj1 y obj2 son referencias a la clase base Figura. Sin
embargo, cuando obj1 llama a la función área, devuelve (correctamente) el área del
rectángulo y cuando, obj2 llama a la función área devuelve el área del círculo. Además
aunque PI es un miembro estático de la clase Circulo, se reconstruye el objeto obj2 con
el valor del miembro estático con el que se calcula el área del círculo

Serialización Personalizada.

El proceso de serialización proporcionado por el lenguaje Java es suficiente para


la mayor parte de las clases, ahora bien, se puede personalizar para aquellos casos
específicos. Para personalizar la serialización, es necesario definir dos funciones
miembros writeObject y readObject. El primero, controla que información es enviada al
flujo de salida. La segunda, lee la información escrita por writeObject. La definición de
writeObject debe ser la siguiente:

private void writeObject (ObjectOutputStream s) throws IOException{


s.defaultWriteObject();
//...código para escribir datos
}

La función readObject lee todo lo que se ha escrito con writeObject en el mismo orden
en el que se ha escrito. Además, puede realizar otras tareas necesarias para actualizar el
estado del objeto:

private void readObject (ObjectInputStream s) throws IOException{


s.defaultReadObject();
//...código para leer datos
//...
//actualización del estado del objeto, si es necesario
}

Para un control explícito del proceso de serialización la clase implementa la interfaz


Externalizable. La clase es responsable de escribir y de leer su contenido, y debe estar
coordinada con sus clases base para hacer esto. La definición de la interfaz
Externalizable es la siguiente:

packege java.io;

public interface Externalizable extends Serializable{


public void writeExternal(ObjectOutput out) throws IOException;
public void readExternal(ObjectOutput in) throws IOException,
java.lang.ClassNotFoundException;;
}

4. Gestión de ficheros.

Los ficheros son una fuente y destino primario de datos dentro de muchos
programas. La clase File trata directamente con ficheros y sistemas de fichero. Un
objeto file se usa para obtener o manipular la información asociada a un fichero en
disco, como los permisos, fecha, hora y directoria y para navegar por la jerarquía de
subdirectorios.

Par crear los objetos File pueden usarse los siguientes constructores:

File(String directorio)
File(String directorio, String nombreFich)
File(File dirObj, String nombreFich)
File(URI uriObj)

donde directorio es el nombre del directorio, nombreFich es el nombre del fichero,


dirObj es un objeto File que especifica un directorio y uriObj es un objero URI que
describe un fichero.

Ejemplo:

File f1 = new File(“/”);


File f2 = new File(“/”,”autoexec.bat”);
File f3 = new File(f1,”autoexec.bat”);

El constructor utilizado depende a menudo de otros objetos File necesarios para


el acceso. Por ejemplo, si sólo se utiliza un fichero en la aplicación, el primer
constructor es el mejor. Si en cambio, se utilizan muchos ficheros desde un mismo
directorio, el segundo o tercer constructor serán más cómodos. Y si el directorio o el
fichero es una variable, el segundo constructor será el más útil.

Una vez creado un objeto File, se puede utilizar uno de los siguientes métodos
para reunir información sobre el fichero:

Método Descripción
Nombres de fichero
String getName() Devuelve el nombre del fichero.
String getPath() Devuelve la ruta.
String getAbsolutePath() Devuelve la ruta absoluta.
String getParent() Devuelve el directorio padre.
Comprobaciones
boolean exists() Devuelve true si el fichero existe; en caso
contrario, devuelve false.
boolean canWrite() Devuelve true si se puede sobrescribir el
fichero y false en caso contrario.
boolean canRead() Devuelve true si el fichero es de lectura y
false si no lo es.
boolean isFile() Devuelve true si se le llama a través de un
fichero y false si se le llama a través de un
directorio.
boolean isDirectory() Devuelve true si el fichero es un directorio
y false si no lo es.
boolean isAbsolute() Devuelve true si el fichero tiene un
directorio absoluto si false si el directorio
es relativo.
Información general del fichero
long lastModified() Devuelve la fecha de la última
modificación del fichero.
long length() Devuelve la longitud del fichero.
Utilidades del directorio
boolean mkdir() Crea un directorio. Devuelve true si el
proceso tiene éxito y false si se produce
un fallo.
boolean mkdirs() Crea directorio y directorio padre.
String[] list() Extrae la lista de otros ficheros
Otros métodos de gran utilidad
boolean renameTo(File nuevoNombre) Renombra el fichero con el nombre
especificado en el nuevoNombre del
fichero File que realiza la llamada. Se
devolverá true en caso de que la operación
se realice con éxito y false si no se puede
cambiar el nombre del fichero.
boolean delete() Elimina el fichero de disco representado
por el objeto File que realiza la llamada.
También se puede utilizar delete() para
eliminar un directorio vacío. Devuelve
true si se consigue eliminar el fichero y
false en caso contrario.
void deleteOnExit() Elimina el fichero asociado al objeto que
llama cunado JVM termina.
boolean isHidden() Devuelve trae si el objeto que realiza la
llamada es un fichero oculto. De lo
contrario, devuelve false.
boolean setLastModified(long miliseg) Establece la hora en un fichero que realiza
la llamada que especifica miliseg, que es
el número de milisegundos desde la
medianoche del 1/1/1970 UTC.
boolean setReadOnly() Establece el fichero invocante en modo de
sólo lectura.
En el siguiente ejemplo se muestra información sobre los ficheros pasados como
argumentos en la línea de comandos:
import java.io.*;

class InfoFichero {

public static void main( String args[] ) throws IOException {


if( args.length > 0 )
{
for( int i=0; i < args.length; i++ )
{
File f = new File( args[i] );
System.out.println( "Nombre: "+f.getName() );
System.out.println( "Camino: "+f.getPath() );
if( f.exists() )
{
System.out.print( "Fichero existente " );
System.out.print( (f.canRead() ?
" y se puede Leer" : "" ) );
System.out.print( (f.canWrite() ?
" y se puese Escribir" : "" ) );
System.out.println( "." );
System.out.println( "La longitud del fichero son "+
f.length()+" bytes" );
}
else
System.out.println( "El fichero no existe." );
}
}
else
System.out.println( "Debe indicar un fichero." );
}
}

A menudo se desea limitar el número de ficheros devueltos por el método list()


para incluir sólo aquellos ficheros que coincidan con un cierto patrón o filtro. Para ello,
se usa una segunda forma de list():

String[] list (FilenameFilter FFObj)

En esta forma, FFObj es un objeto de una clase que implementa la interfaz


FilenameFilter. FilenameFilter define un único método, accept(), al que se llama una
vez por cada uno de los archivos de la lista. Su forma general es la siguiente:

Boolean accept(File directorio, String NombreFichero)

El método accept() devuelve true para los ficheros que especifique directorio que
deben incluirse en la lista y devuelve false para aquellos ficheros que deban exluirse.

En el siguiente ejemplo, la clase OnlyExt, implementa FilenameFilter. Sólo se


visualizarán los nombres de los ficheros que terminen con la extensión especificada al
construir el objeto:

import java.io.*;

public class OnlyExt implements FilenameFilter {


String ext;

public OnlyExt(String ext) {


this.ext = “.” + ext;
}

public Boolean accept(File dir, String name) {


return name.endsWith(ext);
}
}

A continuación se muestra el programa de listado modificado. Ahora sólo


mostrará ficheros con extensión .html.

// Directorio de ficheros .html


import java.io.*;

class DirListOnly {
public static void main(String args[]) {
String dirname = “/java”;
File f1 = new File(dirname);
FilenameFilter only = new OnlyExt(“html”);
String s[] = f1.list(only);

For (int i=0; I > s.length; i++) {


System.out.println(s[
i]};
}
}
}

Una variación del método list() es listFiles(), que puede resultal muy útil. Sus
formas son las siguientes:

File[] listFiles();
File[] listFiles(FilenameFilter FFObj);
File[] listFiles(FileFilter FObj);

Estos métodos devuelven la lista del fichero como una matriz de objetos File en
lugar de cómo cadenas. El primer método devuelve todos los ficheros, el segundo
devuelve sólo aquellos ficheros que satisfacen el FileName especificado. Además de
devolver una matriz de objetos File, estas dos versiones de listFiles() funcionan como
sus métodos list() equivalentes. La tercera versión de listFiles() devuelve aquellos
ficheros cuyos nombres de directorios satisfagan el filtro FileFilter especificado.
FileFilter define un solo método, accept(), al que se le llama una vez por cada fichero de
lista. Su forma general es la siguiente:

Boolean accept(File directorioFichero);

El método accept() devuelve true para los ficheros que deberían incluirse en la
lista (es decir, aquellos que coinciden con el argumento de directorioFichero), y false
para aquellos que deberían exluirse.
La versión J2SE 5 incorpora dos nuevas interfaces en java.io, Closeable y
Flushable. Estas interfaces están implementadas por algunas de las clases de E/S. Su
inclusión no aporta nuevas funciones a las clases de flujo, sino que simplemente ofrece
una manera uniforme de especificar que un flujo puede cerrarse o purgarse.
Los objetos de una clase que implemente Closeable pueden cerrarse. Esta interfaz
define el método close() :

void close() throws IOException

Este método cierra el flujo que realiza la llamada, liberando cualquier recurso
que pudiera contener. Esta interfaz está implementada por todas las clases de E/S que
abren un flujo que también puede cerrarse.

Los objetos de una clase que implementa Flushable pueden ser salidas de búfer
obligadas a escribirse en el flujo al que el objeto se ha adjuntado. Esta interfaz define el
método flush():

Void flush() throw IOException

Purgar un flujo suele hacer que la salida por búfer sea físicamente escrita en el
elemento subyacente. Esta interfaz está implementada por todas las clases de E/S que
escriben en el flujo.