Introducción
Normalmente,
cuando se codifica un programa, se hace con la intención de que ese programa
pueda interactuar con los usuarios del mismo, es decir, que el usuario pueda
pedirle que realice cosas y pueda suministrarle datos con los que se quiere que
haga algo. Una vez introducidos los datos y las órdenes, se espera que el
programa manipule de alguna forma esos datos para proporcionarnos una respuesta
a lo solicitado.
Además, en
muchas ocasiones interesa que el programa guarde los datos que se le han
introducido, de forma que si el programa termina los datos no se pierdan y
puedan ser recuperados en una sesión posterior. La forma más normal de hacer
esto es mediante la utilización de ficheros que se guardarán en un dispositivo
de memoria no volátil (normalmente un disco).
A todas estas
operaciones, que constituyen un flujo de información del programa con el
exterior, se les conoce como Entrada/Salida (E/S).
Existen dos
tipos de E/S; la E/S estándar que se realiza con el terminal del usuario
y la E/S a través de fichero, en la que se trabaja con ficheros de
disco.
Todas las
operaciones de E/S en Java vienen proporcionadas por el paquete estándar de la
API de Java denominado java.io que incorpora interfaces, clases y
excepciones para acceder a todo tipo de ficheros. En este tutorial sólo se van a
dar algunas pinceladas de la potencia de este paquete.
Entrada/Salida
estándar
Aquí sólo
trataremos la entrada/salida que se comunica con el usuario a través de la
pantalla o de la ventana del terminal.
Si creamos una
applet no se utilizarán normalmente estas funciones, ya que su resultado
se mostrará en la ventana del terminal y no en la ventana de la applet.
La ventana de la applet es una ventana gráfica y para poder realizar una
entrada o salida a través de ella será necesario utilizar el AWT.
El acceso a la
entrada y salida estándar es controlado por tres objetos que se crean
automáticamente al iniciar la aplicación: System.in, System.out y
System.err
Este objeto
implementa la entrada estándar (normalmente el teclado). Los métodos que nos
proporciona para controlar la entrada son:
- read(): Devuelve el carácter que se ha introducido por el teclado leyéndolo del buffer de entrada y lo elimina del buffer para que en la siguiente lectura sea leído el siguiente carácter. Si no se ha introducido ningún carácter por el teclado devuelve el valor -1.
- skip(n): Ignora los n caracteres siguientes de la entrada.
Este objeto
implementa la salida estándar. Los métodos que nos proporciona para controlar la
salida son:
- print(a): Imprime a en la salida, donde a puede ser cualquier tipo básico Java ya que Java hace su conversión automática a cadena.
- println(a): Es idéntico a print(a) salvo que con println() se imprime un salto de línea al final de la impresión de a.
Este objeto
implementa la salida en caso de error. Normalmente esta salida es la pantalla o
la ventana del terminal como con System.out, pero puede ser interesante
redirigirlo, por ejemplo hacia un fichero, para diferenciar claramente ambos
tipos de salidas.
Las funciones
que ofrece este objeto son idénticas a las proporcionadas por
System.out.
Ejemplo
A continuación
vemos un ejemplo del uso de estas funciones que acepta texto hasta que se pulsa
el retorno de carro e informa del número de caracteres
introducidos.
import java.io.*;
class
CuentaCaracteres {
public static void main(String args[]) throws IOException
{
int
contador=0;
while(System.in.read()!='\n')
contador++;
System.out.println();
// Retorno de carro "gratuito"
System.out.println("Tecleados
"+contador+" caracteres.");
}
}
Entrada/Salida
por fichero
Tipos de
ficheros
En Java es
posible utilizar dos tipos de ficheros (de texto o binarios) y dos tipos de
acceso a los ficheros (secuencial o aleatorio).
Los ficheros de
texto están compuestos de caracteres legibles, mientras que los binarios pueden
almacenar cualquier tipo de datos (int, float,
boolean,...).
Una lectura
secuencial implica tener que acceder a un elemento antes de acceder al
siguiente, es decir, de una manera lineal (sin saltos). Sin embargo los ficheros
de acceso aleatorio permiten acceder a sus datos de una forma aleatoria, esto es
indicando una determinada posición desde la que leer/escribir.
Clases a
estudiar
En el paquete
java.io existen varias clases de las cuales podemos crear instancias de
clases para tratar todo tipo de ficheros.
En este tutorial
sólo vamos a trata las tres principales:
- FileOutputStream: Fichero de salida de texto. Representa ficheros de texto para escritura a los que se accede de forma secuencial.
- FileInputStream: Fichero de entrada de texto. Representa ficheros de texto de sólo lectura a los que se accede de forma secuencial.
- RandomAccessFile: Fichero de entrada o salida binario con acceso aleatorio. Es la base para crear los objetos de tipo fichero de acceso aleatorio. Estos ficheros permiten multitud de operaciones; saltar hacia delante y hacia atrás para leer la información que necesitemos en cada momento, e incluso leer o escribir partes del fichero sin necesidad de cerrarlo y volverlo a abrir en un modo distinto.
Generalidades
Para tratar con
un fichero siempre hay que actuar de la misma manera:
1. Se abre el
fichero. Para ello hay
que crear un objeto de la clase correspondiente al tipo de fichero que vamos a
manejar, y el tipo de acceso que vamos a utilizar:
TipoDeFichero
obj = new TipoDeFichero( ruta );
Donde
ruta es la ruta de disco en que se encuentra el fichero o un descriptor
de fichero válido. Este formato es válido, excepto para los objetos de la clase
RandomAccessFile (acceso aleatorio), para los que se ha de instanciar de
la siguiente forma:
RandomAccessFile obj = new RandomAccessFile( ruta, modo
);
Donde
modo es una cadena de texto que indica el modo en que se desea abrir el
fichero; "r" para sólo lectura o "rw" para lectura y escritura.
2. Se utiliza el
fichero. Para ello cada
clase presenta diferentes métodos de acceso para escribir o leer en el
fichero.
3. Gestión de
excepciones (opcional, pero recomendada) Se puede
observar que todos los métodos que utilicen clases de este paquete deben tener
en su definición una cláusula throws IOException. Los métodos de estas
clases pueden lanzar excepciones de esta clase (o sus hijas) en el transcurso de
su ejecución, y dichas excepciones deben de ser capturadas y debidamente
gestionadas para evitar problemas.
4. Se cierra el
fichero y se destruye el objeto. Para cerrar un
fichero lo que hay que hacer es destruir el objeto. Esto se puede realizar de
dos formas, dejando que sea el recolector de basura de Java el que lo destruya
cuando no lo necesite (no se recomienda) o destruyendo el objeto explícitamente
mediante el uso del procedimiento close() del objeto:
obj.close()
La clase
FileOutputStream
Mediante los
objetos de esta clase escribimos en ficheros de texto de forma
secuencial.
Presenta el
método write() para la escritura en el fichero. Presenta varios
formatos:
- int write( int c ): Escribe el carácter en el fichero.
- int write( byte a[] ): Escribe el contenido del vector en el fichero.
- int write( byte a[], int off, int len ): Escribe len caracteres del vector a en el fichero, comenzando desde la posición off.
El siguiente
ejemplo crea el fichero de texto "/carta.txt" a partir de un texto que se
le introduce por teclado:
import java.io.*;
class
CreaCarta {
public static void main(String args[]) throws
IOException{
int
c;
FileOutputStream f=new FileOutputStream("/carta.txt");
while( ( c=System.in.read() ) != -1 )
f.write( (char)c );
f.close();
}
}
Mediante los
objetos de esta clase leemos de ficheros de texto de forma
secuencial.
Presenta el
método read() para la lectura del fichero. Este método se puede invocar
de varias formas.
- int read(): Devuelve el siguiente carácter del fichero.
- int read( byte a[] ): Llena el vector a con los caracteres leídos del fichero. Devuelve la longitud del vector que se ha llenado si se realizó con éxito o –1 si no había suficientes caracteres en el fichero para llenar el vector.
- int read( byte a[], int off, int len ): Lee len caracteres del fichero, insertándolos en el vector a.
Todos ellos
devuelven -1 si se ha llegado al final del fichero (momento de
cerrarle).
El siguiente
ejemplo muestra el fichero de texto "/carta.txt" en
pantalla:
import
java.io.*;
class
MuestraCarta {
public static void main(String args[]) throws IOException
{
int
c;
FileInputStream f=new FileInputStream("/carta.txt");
while( ( c=f.read() ) != -1 )
System.out.print( (char)c );
f.close();
}
}
Mediante los
objetos de esta clase utilizamos ficheros binarios mediante un acceso aleatorio,
tanto para lectura como para escritura. En estos ficheros hay un índice que nos
dice en qué posición del fichero nos encontramos, y con el que se puede trabajar
para posicionarse en el fichero.
Métodos de
desplazamiento
Cuenta con una
serie de funciones para realizar el desplazamiento del puntero del fichero. Hay
que tener en cuenta que cualquier lectura o escritura de datos se realizará a
partir de la posición actual del puntero del fichero.
- long getFilePointer();Devuelve la posición actual del puntero del fichero.
- void seek( long l ); Coloca el puntero del fichero en la posición indicada por l. Un fichero siempre empieza en la posición 0.
- int skipBytes( int n ); Intenta saltar n bytes desde la posición actual.
- long length(); Devuelve la longitud del fichero.
- void setLength( long l); Establece a l el tamaño de este fichero.
- FileDescriptor getFD(); Devuelve el descriptor de este fichero.
Métodos de
escritura
La escritura del
fichero se realiza con una función que depende el tipo de datos que se desee
escribir.
- void write( byte b[], int ini, int len ); Escribe len caracteres del vector b.
- void write( int i ); Escribe la parte baja de i (un byte) en el flujo.
- void writeBoolean( boolean b ); Escribe el boolean b como un byte.
- void writeByte( int i ); Escribe i como un byte.
- void writeBytes( String s ); Escribe la cadena s tratada como bytes, no caracteres.
- void writeChar( int i ); Escribe i como 1 byte.
- void writeChars( String s ); Escribe la cadena s.
- void writeDouble( double d ); Convierte d a long y le escribe como 8 bytes.
- void writeFloat( float f ); Convierte f a entero y le escribe como 4 bytes.
- void writeInt( int i ); Escribe i como 4 bytes.
- void writeLong( long v ); Escribe v como 8 bytes.
- void writeShort( int i ); Escribe i como 2 bytes.
- void writeUTF( String s ); Escribe la cadena s utilizando la codificación UTF-8.
Los métodos que
escriben números de más de un byte escriben el primero su parte
alta.
Métodos de
lectura
La lectura del
fichero se realiza con una función que depende del tipo de datos que queremos
leer.
- boolean readBoolean(); Lee un byte y devuelve false si vale 0 o true sino.
- byte readByte(); Lee y devuelve un byte.
- char readChar(); Lee y devuelve un caracter.
- double readDouble(); Lee 8 bytes, y devuelve un double.
- float readFloat(); Lee 4 bytes, y devuelve un float.
- void readFully( byte b[] ); Lee bytes del fichero y los almacena en un vector b.
- void readFully( byte b[], int ini, int len ); Lee len bytes del fichero y los almacena en un vector b.
- int readInt(); Lee 4 bytes, y devuelve un int.
- long readLong(); Lee 8 bytes, y devuelve un long.
- short readShort(); Lee 2 bytes, y devuelve un short.
- int readUnsignedByte(); Lee 1 byte, y devuelve un valor de 0 a 255.
- int readUnsignedShort(); Lee 2 bytes, y devuelve un valor de 0 a 65535.
- String readUTF(); Lee una cadena codificada con el formato UTF-8.
- int skipBytes(int n); Salta n bytes del fichero.
Si no es posible
la lectura devuelven –1.
Ejemplo
Vamos a crear un
pequeño programa que cree y acceda a un fichero binario, mediante acceso
aleatorio. El siguiente ejemplo crear un fichero binario que contiene los 100
primeros números (en orden):
// Crea un
fichero binario con los 100 primeros numeros
static void creaFichBin( String ruta ) throws IOException
{
RandomAccessFile f=new RandomAccessFile(ruta,"rw"); //
E/S
for (
int i=1; i <= 100 ; i++ )
{
try{
f.writeByte( i );
}
catch( IOException e){
// Gestion de
excepcion de ejemplo
break; // No se
puede seguir escribiendo
}
f.close();
}
}
El siguiente
método accede al elemento cual de un fichero binario, imprimiendo la
longitud del fichero, el elemento cual y su 10 veces siguiente
elemento:
static void imprimeEltoN(String ruta, long cual)
throws IOException{
RandomAccessFile f=new RandomAccessFile(ruta,"r"); // E/
System.out.print( "El fichero " + ruta );
System.out.println( " ocupa " + f.length() + " bytes." );
f.seek( cual-1
); // Me posiciono (-1 porque empieza en 0)
System.out.print("
En la posicion " + f.getFilePointer() );
System.out.println("
esta el numero " + f.readByte() );
f.skipBytes( 9
); // Salto 9 => Elemento 10 mas alla
System.out.print("
10 elementos más allá, esta el ");
System.out.println( f.readByte() );
f.close();
}
Si incluimos
ambos métodos en una clase, y les llamamos con el siguiente programa principal
(main()):
public static void main(String args[]) throws IOException
{
String
ruta="numeros.dat"; // Fichero
creaFichBin(
ruta ); // Se crea
imprimeEltoN(
ruta, 14 ); // Accedo al elemento 14.
}
Obtendremos la
siguiente salida:
El fichero
numeros.dat ocupa 100 bytes.
En la posicion
13 esta el numero 14
10 elementos más
allá, esta el 24
No hay comentarios:
Publicar un comentario