Está en la página 1de 11

Tema 7: Entrada/Salida

Todo programa procesa unos dados de entrada, y produce una salida. Por ello, resulta fundamental el conocer las distintas formas de entrada/salida que se pueden dar en un programa. En este tema, analizaremos los mecanismos que proporciona el lenguaje Java para proporcionar datos de entrada y de salida.

1. Archivos y flujos
El trmino "fichero" o "archivo" es una abstraccin que representa la infomacin que se almacena fsicamente en algn dispositivo, como un disco duro o un disquette. Podemos pensar en un fichero como en una coleccin de informacin relacionada lgicamente, como el cdigo fuente de un programa, o el conjunto de datos de los empleados de una empresa. Java considera los archivos como flujos secuenciales de bytes. Cada archivo termina con un marcador de fin de archivo o bien en un nmero de byte especfico registrado en una estructura de datos mantenida por el sistema. Para poder leer o escribir en un archivo, es necesario "abrirlo" primero. Cuando se abre un archivo, se crea un objeto y se asocia un flujo1 (stream) a dicho objeto. Cuando comenzamos a ejecutar una aplicacin o un applet de Java, se crean automticamente tres objetos de flujo: System.in, System.out y System.err. Los flujos asociados a estos objetos proporcionan canales de comunicacin entre un programa y un archivo o dispositivo en particular. Por ejemplo, el objeto System.in (objeto de flujo de entrada estndar) permite a un programa introducir bytes desde el teclado, el objeto System.out (objeto de flujo de salida estndar) permite a un programa enviar datos a la pantalla, y el objeto System.err (objeto de flujo de error estndar) permite a un programa enviar mensajes de error a la pantalla. Si queremos procesar archivos en Java necesitamos importar el paquete java.io. En la Figura 7.1 aparece representada parte de la jerarqua de clases relacionada con la entrada/salida de datos. Este paquete incluye las definiciones de las clases de flujos como FileInputStream (para el flujo de entrada de un archivo) y FileOutputStream (para el flujo de salida a un archivo). Los archivos se abren creando objetos de estas clases de flujo que se derivan de las clases InputStream y OutputStream, respectivamente. Las clases InputStream y OutputStream son clases abstractas que definen mtodos para realizar operaciones de entrada y salida. Es importante destacar que los objetos InputStream poseen un mtodo de lectura, read(), que tiene el efecto lateral de eliminar el byte o bytes ledos, en vez de copiarlos. Otro mtodo interesante implementado por las subclases de InputStream es el mtodo close(), ste libera los recursos utilizados por el sistema, y como consecuencia, hace que sean imposibles posteriores operaciones de lectura. Siempre se debe cerrar un objeto InputStream, mediante el mtodo close() , cuando se haya dejado de utilizar. Las signaturas de dichos mtodos son:
1

Se denomina flujo a una secuencia de datos que proviene de una determinada fuente. As, el teclado se modela como un flujo, al igual que un archivo de disco.

Tema 7. Entrada/Salida

int read() throws IOException void close() throws IOException

Object File InputStream FileInputStream FilterInputStream DataInputStream OutputStream FileOutputStrea FilterOutputStream DataOutputStream Figura 7.1. Parte de la jerarqua de clases del paquete java.io El mtodo skip(n) permite descartar un cierto nmero de bytes del flujo de datos, tal y como si hubiesen sido ledos. Este mtodo devuelve el nmero real de bytes descartados, ya que, puede que no haya n bytes disponibles en el flujo. Su signatura es: long skip(long n) throws IOException La clase OutputStream es similar a la clase InputStream. Una instancia de OutputStream proporciona un lugar en donde nuestro programa puede escribir uno o ms bytes. Tambin dispone del mtodo close(), el cual, una vez ejecutado, no permite realizar ms operaciones sobre dicho flujo. Siempre se debe cerrar un objeto OutputStream, mediante el mtodo close() , cuando se haya dejado de utilizar.

1.1. Las clases DataInputStream y DataOutputStream Estas dos clases resultan muy tiles, puesto que en la gran mayora de aplicaciones que necesitan entrada y/o salida de datos, se querr leer o escribir elementos ms complejos que simples bytes. Por ejemplo, podemos querer leer o escribir en entero, en lugar de tener que leer

Programacin para la Gestin

4 bytes y convertirlos a un valor entero. Estas clases nos van a permitir realizar operaciones de entrada y salida de tipos de datos. Las clases DataInputStream y DataOutputStream pueden verse como clases "wrapper"2 para InputStream y OutputStream, tienen acceso a todos los mtodos de sus clases padre, y aaden las funcionalidades necesarias para leer o escribir cualquier tipo primitivo Java. La clase DataInputStream (flujo de entrada de datos) tiene un nico constructor: DataInputStream (InputStream in) que construye un nuevo DataInputStream, usando el argumento InputStream como fuente. Todos los mtodos de DataInputSream tienen el modificador final, lo cual significa que no podemos sobre escribirlos si creamos nuestras propias subclases de DataInputStream. Adems, todos ellos pueden ocasionar una excepcin IOException. Algunos de los mtodos disponibles son: boolean readBoolean() throws IOException Un valor 0 representa false, y cualquier otro valor representa true. Este mtodo lee un byte del flujo de datos y retorna el valor booleano asociado. byte readByte() throws IOException Lee y devuelve un nico byte. Se considera que dicho byte tiene signo, por lo que puede representar cualquier valor entre 128 y 127 char readChar() throws IOException Lee dos bytes y devuelve el correspondiente carcter. double readDouble() throws IOException Lee ocho bytes y devuelve el correspondiente valor double. float readFloat() throws IOException Lee cuatro bytes y devuelve el correspondiente valor float. int readInt() throws IOException Lee cuatro bytes y devuelve el correspondiente valor int. long readLong() throws IOException Lee ocho bytes y devuelve el correspondiente valor long. short readShort() throws IOException Lee dos bytes y devuelve el correspondiente valor short. La clase DataOutputStream (flujo de salida de datos) dispone de mtodos similares a los anteriores, pero para escritura, por ejemplo writeBoolean(), writeByte(),... Como veremos en el apartado siguiente, para realizar operaciones de entrada y salida de tipos de datos primitivos, se usarn objetos de las clases DataInputStream y DataOutputStream junto con las clases de flujos de archivos.

2. Entrada y salida de archivos


Los archivos nos van a permitir almacenar fsicamente un conjunto de datos relacionados lgicamente. En Java, los programadores deben estructurar los archivos de forma que satisfagan las necesidades de las aplicaciones.

Recordemos que hablamos de clases wrapper en el tema anterior, en el que asocibamos una clase wrapper a cada tipo primitivo (p.ej. la clase Integer es clase wrapper para el tipo primitivo int)

Tema 7. Entrada/Salida

Para usar un archivo como una fuente de bytes para lectura o como escritura, Java proporciona las clases FileInputStream y FileOutputStream. FileInputStream tiene tres constructores, de los cuales mostraremos dos: FileInputStream (String nombreFichero) throws FileNotFoundException, SecurityException FileInputStream (File f) throws FileNotFoundException, SecurityException El primero de ellos crea un nuevo FileInputStream asociado al fichero cuyo nombre se especifica como argumento, y el segundo crea una nueva instancia asociada al argumento File. FileOutputStream se define de forma similar. Un FileInputStream es un InputStream, debido a la herencia, de forma que puede usarse como argumento en el constructor de DataInputStream. Haciendo esto, se pueden utilizar los mtodos de DataInputStream para leer cualquier tipo primitivo de un fichero. Para abrir un fichero con un nombre determinado, todo lo que se necesita hacer es: Abrir un fichero para lectura FileInputStream fstr = new FileInputStream(nombre); DataInputStream in = new DataInputStream(fstr); o bien podramos haber eliminado la primera instruccin, de forma que: DataInputStream in = new DataInputStream(new FileInputStream(nombre)); Si lo que se quiere es escribir en un fichero, debemos utilizar FileOutputStream Abrir un fichero para escritura FileOutputStream fstr = new FileOutputStream(nombre); DataOutputStream in = new DataOutputStream(fstr);

2.1 Entrada/Salida de tipos primitivos Una vez que hemos construido un FileOutputStream y lo hemos usado para construir un DataOutputStream, disponemos de los mtodos write() de esta ltima clase. Como ejemplo supongamos el siguiente listado (Listado 7.1) que crea un nuevo fichero llamado "Ejemplo", escribe una cadena de caracteres, un valor double, y un entero, y cierra el fichero.

Listado 7.1. Creacin y escritura en un fichero void escribirFichero(String s, double d, int n) { //primero creamos el fichero FileOutputStream f = new FileOutputStream("Ejemplo"); DataOutputStream out = new DataOutputStream(f); //ahora escribimos la informacin out.writeInt(s.length()); out.writeChars(s); out.writeDouble(d); out.writeInt(n); //ahora cerramos el flujo 4

Programacin para la Gestin

out.close(); } Si analizamos el listado 7.1 observamos que hemos escrito la longitud de la cadena de caracteres antes de escribirla. La razn es que, si bien podemos escribir una cadena de caracteres utilizando el mtodo writeChars(), no hay forma de leer una cadena de caracteres si no es carcter a carcter. De forma que necesitaremos saber cuntos bytes deberemos leer para completar la cadena de caracteres. Una vez que terminamos la escritura, el fichero se asemejara al siguiente:
marca de fin de fichero

4 bytes para la longitud

2k bytes para s de longitud k

8 bytes para d

4 bytes para n

La marca de fin de fichero es meramente conceptual, para indicar que Java tiene una forma especial de saber dnde termina el fichero, bien contando el nmero de bytes, o mediante un carcter especial. No se trata de un byte "real" en el sentido de que lo podamos leer o inspeccionar de alguna manera. Si ussemos un editor de texto para abrir el fichero "Ejemplo", podramos ver la cadena de caracteres, precedida y seguida por un montn de caracteres incomprensibles. Ello es debido a que los valores double e int se guardan en su representacin binaria, en lugar de en su representacin equivalente con caracteres. Para leer el contenido del fichero se hace de forma similar a como hemos hecho para escribir la informacin, deberamos primero leer el entero, a continuacin los caracteres de la cadena, seguidos por el valor double, y finalmente el entero. Es muy importante leer la informacin en el mismo orden en que se ha escrito. El listado 7.2 muestra el cdigo para leer los datos: Listado 7.2. Lectura de datos en un fichero String s; double d; int n; ... void leerFichero() { //primero creamos el fichero FileInputStream f = new FileInputStream("Ejemplo"); DataInputStream in = new DataInputStream(f); //ahora leemos la informacin, en el orden en el que //fue escrita int len = in.readInt(); char[] buffer = new char[len]; for (int i=0; i < len; i++) buffer[i]=in.readChar; s = new String(buffer); d = in.readDouble; n = in.ReadInt(); 5

Tema 7. Entrada/Salida

//ahora cerramos el flujo in.close(); } Si no hubisemos escrito la longitud de la cadena de caracteres, no podramos haber ledo correctamente dicha cadena de caracteres. Es decir, no hubisemos podido saber dnde termina la cadena de caracteres, y donde empieza el valor double. Podemos observar que en el listado aparece un nuevo tipo de datos, char[ ]. Dicho tipo se estudiar en captulos posteriores, ahora simplemente diremos que indica una secuencia ordenada de caracteres, a la que se puede acceder mediante la posicin que ocupan, as buffer[0] indica el carcter que ocupa la primera posicin, buffer[1] el carcter que ocupa la segunda posicin,..., y as sucesivamente.

2.2 Entrada/Salida de objetos de una clase No es mucho ms difcil leer y escribir objetos pertenecientes a alguna clase, que la lectura y escritura de tipos primitivos. Lo nico que debemos hacer es aplicar los mtodos adecuados de DataInputStream o DataOutputStream a los datos miembros de la clase correspondiente. A menudo es conveniente proporcionar una clase con los mtodos que pueden usarse para leer y escribir. A continuacin mostramos un listado (Listado 7.3) en el que se usan los mtodos readFrom(), y writeTo()3. Listado 7.3. Lectura y escritura de objetos pertenecientes a una clase class Record { private String s; private double d; private int n; public Record(String s, double d, int n) { this.s = new String(s); this.d = d; this.n = n; this.s = s; } public void readFrom(DataInputStream in) throws IOException { int len = in.readInt (); char[] buffer = new char[len]; for (int i = 0; i < len; i++) buffer[i] = in.readChar(); s = new String(buffer); d = in.readDouble(); n = in.readInt(); } public void writeTo(DataOutputStream out) trhows IOException { out.writeInt(s.length());
3

Existen las clases ObjectInputStream y ObjectOutputStream, diseadas para leer y escribir objetos de varias clases, independientemente de como estn representados sus datos miembros. Sin embargo, no vamos a tratar dichas clases aqu.

Programacin para la Gestin

out.writeBytes(s); out.writeDouble(d); out.writeInt(n); } int len = in.readInt(); char[] buffer = new char[len]; for (int i=0; i < len; i++) buffer[i]=in.readChar; s = new String(buffer); d = in.readDouble; n = in.ReadInt(); //ahora cerramos el flujo in.close(); } } Podramos usar esta clase Record en un applet o aplicacin construyendo el flujo adecuado y usndolo para entrada o salida. Para escribir un fichero llamado "Ejemplo2" que contenga un objeto Record, podramos hacer lo siguiente (ver Listado 7.4). Listado 7.4. Escritura en un fichero de objetos pertenecientes a una clase Record myRecord = new Record("Jueves",34.09, 2001); ... private void grabar() { try { FileOutputStream f= new FileOutputStream("Ejemplo2"); DataOutputStream out= new DataOutputStream(f); myRecord.writeTo(out); out.close(); } catch (IOException ex) { System.err.println(ex); } } Para leer los valores grabados en el fichero "Ejemplo2" y usarlos para actualizar los datos de myRecord, podramos crear un mtodo, tal y como se muestra en el Listado 7.5 Listado 7.5. Lectura en un fichero de objetos pertenecientes a una clase private void abrir() { try { FileInputStream f= new FileInputStream("Ejemplo2"); DataIntputStream in= new DataIntputStream(f); myRecord.readFrom(in); in.close(); } catch (IOException ex) { System.err.println(ex); } } Para escribir, y posteriormente leer varios objetos en un fichero, podemos adoptar la solucin que hemos utilizado para las cadenas de caracteres, es decir, incluir como primer elemento de informacin del fichero, el nmero de elementos de dicho fichero. De esta forma sabremos el nmero de elementos que deberemos leer, una vez abierto el fichero.

Tema 7. Entrada/Salida

Sin embargo, se puede proceder de otra forma igualmente vlida. Consiste en leer los elementos del fichero hasta que lleguemos al final de dicho fichero y no queden ms elementos por leer. Si cualquiera de los mtodos read() de DataInputStream, encuentra el final del fichero antes de que obtenga suficientes bytes para completar su operacin, entonces se genera la excepcin EOFException. Todo lo que debemos hacer es capturar dicha excepcin, lo cual indicar que se ha alcanzado el final del fichero. El mtodo abrir() del Listado 7.5 quedara como se muestra en el Listado 7.6. Listado 7.6. Lectura de un fichero de todos los objetos que lo componen private void abrir() { try { FileInputStream f= new FileInputStream("Ejemplo2"); DataIntputStream in= new DataIntputStream(f); while (true) myRecord.readFrom(in); in.close(); } catch (IOException ex) { System.err.println(ex); } } Los ficheros que hemos visto almacenan la informacin de manera secuencial, de forma que para recuperarla se debe hacer en el mismo orden en que la informacin se ha introducido en el fichero. Si por ejemplo queremos leer el objeto del fichero que ocupa la posicin 10, debemos abrir el fichero y leer los primeros nueve objetos, hasta que finalmente leamos el objetos nmero 10. Por ello, dichos ficheros se dice que son de acceso secuencial. En contraposicin podemos crear ficheros de acceso aleatorio, en los que podemos escribir y leer en la posicin n de dicho fichero, sin tener que acceder primero a los (n-1) elementos anteriores. Para ello se utiliza la clase RandomAccessFile, que nos permitir asociar un flujo con acceso aleatorio a un archivo. Los archivos de acceso aleatorio no van a ser tratados en este tema.

3. Entrada/Salida interactiva
Hablamos a continuacin de los flujos System.in y System.out y System.err que permiten a un programa introducir bytes desde el teclado y enviar datos a la pantalla, respectivamente. Nos centraremos primero en la entrada de datos. Para ello, el flujo System.in debe pasarse como parmetro al constructor de una clase denominada InputStreamReader. Por otro lado, utilizaremos una tcnica consistente en el almacenamiento temporal, para mejorar el rendimiento de las operaciones de entrada/salida (E/S). La idea es utilizar una clase denominada BufferedReader, que permite leer varios elementos del teclado (elementos lgicos) en una nica operacin fsica, que se almacenan en un buffer4 de la memoria. Cuando un programa solicita un nuevo segmento de datos, stos se toman del buffer (lo cual recibe el nombre de "operacin de entrada lgica"). Cuando el buffer queda vaco, se realiza la siguiente operacin real de entrada fsica del dispositivo de entrada para introducir el siguiente grupo de segmentos "lgicos" de datos. As, el nmero de operaciones de entrada fsica reales es
4

Un buffer es un espacio fsico en donde se almacenan datos que sern requeridos con posterioridad a su almacenamiento para ser utilizados por el programa.

Programacin para la Gestin

pequeo en comparacin con el nmero de solicitudes de lectura emitidas por el programa. Por lo tanto: Dado que las operaciones de entrada fsica por lo general son extremadamente lentas en comparacin con las velocidades normales de los procesadores, el almacenamiento temporal de las entradas mejora significativamente el rendimiento en comparacin con las entradas sin almacenamiento temporal. De esta forma, para crear un flujo de datos de entrada por teclado, utilizaremos las siguientes sentencias: Habilitar un flujo de entrada de datos por teclado InputStreamReader istr = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(istr); o bien podramos haber eliminado la primera instruccin, de forma que: BufferedReader br = new BufferedReader (new InputStreamReader(System.in)); No debemos olvidar que previamente debemos importar el paquete java.io, en el que estn definidas las clases anteriores. As, para leer cadenas de caracteres, la clase BufferedReader dispone de un mtodo que leer una lnea entera de texto. El modelo es: Lectura de una cadena de caracteres cadena = br.readLine(); En el Listado 7.7 observamos un ejemplo de entrada de datos por teclado. Listado 7.7. Entrada de datos por teclado ... void felicitaciones { BufferedReader in = new BufferedReader (new InputStreamReader(System.in)); System.out.println("Introduzca su nombre "); String nombre = in.readLine(); System.out.println("Felicidades "+nombre); } Para realizar una salida de datos por pantalla utilizaremos la clase System.out, tal y como ya se ha visto en ejemplos en los temas anteriores. Para escribir una cadena de caracteres por pantalla se usar el mtodo print, o println del flujo System.out.

4. La clase File
Hasta ahora nos hemos centrado en las clases para procesar archivos secuenciales (FileInputStream y FileOutputStream) y para procesar flujos de datos (DataInputStream y DataOutputStream). En este apartado vamos a presentar la clase File, que es de gran utilidad

Tema 7. Entrada/Salida

para recuperar del disco informacin acerca de un archivo o un directorio. Los objetos de la clase File no abren realmente un archivo ni ofrecen funciones de procesamiento de archivos. Un ejemplo del uso de un objeto File es verificar si un archivo existe. Si abrimos un archivo existente para salida empleando un objeto FileOutputStream, se desecha el contenido de ese archivo sin advertencia, lo cual constituye un error comn de programacin. Se puede usar un objeto File para determinar si el archivo ya existe. De ser as, se podra advertir al usuario que est a punto de desechar el contenido original del archivo. Un objeto File se inicializa con uno de tres constructores. El constructor: public File (String nombre) almacena el argumento nombre en el objeto. El nombre puede contener informacin acerca de la trayectoria del archivo (path)5. La trayectoria incluye algunos de (o todos) los directorios, comenzando con el directorio raz (\)6, que conducen a un archivo o directorio especfico. Todos los archivos y directorios de una unidad de disco tienen el mismo directorio raz en su trayectoria. Una trayectoria relativa contiene un subconjunto de los directorios que conducen a un archivo o directorio especfico. Las trayectorias relativas parten del directorio actual en el que se est trabajando. El constructor: public File (String trayectoria, String nombre) utiliza el argumento trayectoria (una trayectoria absoluta o relativa) para localizar el archivo o directorio especificado por nombre. El constructor public File (File directorio, String nombre) utiliza el objeto directorio (una trayectoria absoluta o relativa) previamente creado para localizar el archivo o directorio especificado por nombre. Agunos de los mtodos de la clase File de uso comn son los siguientes: boolean canRead() Devuelve true si una archivo puede leerse; false si no. boolean canWrite() Devuelve true si una archivo puede escribirse; false si no. boolean exists() Devuelve true si el nombre especificado como argumento del constructor File es un archivo o directorio que est en la trayectoria especificada; false si no. boolean isFile() Devuelve true si el nombre especificado como argumento del constructor File es un archivo; false si no. boolean isDirectory() Devuelve true si el nombre especificado como argumento del constructor File es un directorio; false si no. String getName() Devuelve un String con el nombre del archivo o directorio. String getPath()
5 6

La trayectoria de un archivo o un directorio conduce al archivo o al directorio en el disco La cadena de caracteres "\" hace referencia al directorio raz cuando se trata del sistema operativo DOS o Windows. En otros sistemas operativos se utilizan otros caracteres, por ejemplo en Linux se usa la cadena "/"

10

Programacin para la Gestin

Devuelve un String con la trayectoria del archivo o directorio. long length() Devuelve la longitud del archivo en bytes. Si el objeto File representa un directorio, devuelve 0. En el Listado 7.8 aparece un ejemplo de uso de la clase File Listado 7.8. Uso de la clase File ... void nombre_fichero { File fichero; BufferedReader in = new BufferedReader (new InputStreamReader(System.in)); System.out.println("Introduzca un nombre de fichero "); String nombre = in.readLine(); fichero = new File(nombre); if (name.isFile()) System.out.println("OK") else System.out.println("Error"); }

11