Sie sind auf Seite 1von 49

Prlogo

Quien eres En este libro se asume que el lector tiene una bsica familiaridad con Java. Se debe tener completa familiaridad con la sintaxis del lenguaje. Se debe estar cmodo con la programacin orientada a objetos, incluyendo terminologa como instancias, objetos y clases, y se debe saber la diferencia entre estos trminos. Se debe saber que es una referencia y que significa pasar argumentos a mtodos y regresar valores a los mtodos. Se deben haber escrito simples aplicaciones y applets. En su mayor parte, se tratar de mantener los ejemplos relativamente sencillos de manera que se requiera un mnimo entendimiento de otras partes de la librera de clases fuera de las de entradas y salidas (I/O). Esto quiz lleve a burlarse de estos como "ejemplos de juguete". De todas formas, muchos ejemplos son mucho ms fciles de entender y aprender as que con complejos y sofisticados programas que llenen pgina sobre pgina con cdigo de GUI (Interfaz Grfica de Usuario) solo para demostrar un punto de dos lneas a cerca de entradas y salidas (I/O). Ocasionalmente, de todas formas, un ejemplo con GUI es simplemente muy tentador para ignorarlo, como en la clase StreamedTextArea mostrado en el Captulo 2 o la aplicacin "Visor de Archivos" desarrollada en la mayor parte del libro. Se tratar de dejar el material AWT al mnimo, pero una familiaridad con lo bsico de AWT 1.1 ser asumida.

Cuestiones de seguridad Advertencia: El administrador de seguridad de Java (Security Manager) previene casi todos los ejemplos y mtodos discutidos en este libro de trabajar en un applet. Este libro se enfoca mucho en aplicaciones. Hay muy poco que se puede hacer de entradas y salidas (I/O) desde un applet que no sea de confianza sin "escaparse" del administrador de seguridad. El problema quiz no siempre sea obvio (no todos los exploradores web reportan apropiadamente las excepciones de seguridad) pero estarn ah. Hay algunas excepciones. Streams de arreglos de byte y streams de tubera trabajan sin limitacin en applets. Conexiones a la red pueden

ser hechas de vuelta al host de donde viene el applet (y solo a ese host), System.in y System.out quiz sean accesibles desde algunos (pero no todos) exploradores web. En Java 2 y posteriores, hay formas de "relajar" las restricciones en los applets de forma que tengan acceso limitado al sistema de archivos o acceso ilimitado a la red. De todas formas, estas son excepciones, no la regla. Si se puede hacer que un applet trabaje cuando corre "por s solo" y no se logra hacerlo trabajar dentro de un explorador web, el problema es casi seguro un conflicto con el administrador de seguridad del explorador.

Parte I: I/O Bsico


Captulo 1. Introduccin al I/O Entrada y salida, (I/O en ingls, E/S en espaol) para abreviar, son fundamentales para cualquier sistema operativo de computadora o lenguaje de programacin. Solo los teoristas encuentran interesante escribir programas que no requieren entrada o producir salida. Al mismo tiempo, I/O difcilmente califica como uno de los ms "emocionantes" temas en las ciencias de la computacin. Es algo en la parte de atrs, algo que se usa cada da, pero para muchos desarrolladores, no es un tema de mucho "sex appeal". Hay un montn de razones para que los programadores en Java encuentren I/O interesante. Java incluye un particularmente rico conjunto de clases de I/O en el ncleo del API (Interfaz de Programacin de Aplicaciones) sobre todo en el paquete java.io. Para la mayor parte de I/O en Java, se divide en dos tipos: I/O byte- y numero-orientadas, que es manejado por streams de entrada y salida; e I/O de texto y carcter, que es manejado por lectores y escritores. Ambos tipos proveen una abstraccin para fuentes externas de datos y blancos que permitan leer desde y escribirlos, sin importar el tipo exacto de la fuente. Se usan los mismos mtodos para leer desde un archivo que se hace para leer desde la consola o desde una conexin a la red. Pero eso es solo la punta del iceberg. Una vez que se definan las abstracciones que te permiten leer o escribir sin importar de donde

vienen tus datos o a donde van, se pueden hacer muchas poderosas cosas. Se pueden definir streams de I/O que automticamente se compriman, encripten, y se filtren desde un formato de datos a otro, y ms. Una vez que se tengan estas herramientas, los programas podrn enviar datos encriptados o escribir archivos .zip con casi ningn conocimiento de lo que estn haciendo; la criptografa o compresin podrn ser aislados en unas pocas lneas de cdigo que digan, "Oh, s haz esto un flujo de salida encriptado." En este libro, se tomar una completa mirada a todas las partes que facilitan I/O en Java. Esto incluye todos los diferentes tipos de streams que se puedan usar. Adems, se investigar el soporte de Java para Unicode (el conjunto de caracteres estndar multilenguaje). Vamos a ver las poderosas comodidades de Java para dar formato a I/O (que extraamente, no es parte del paquete java.io en s). Se vern las razones para estas decisiones de diseo luego. Finalmente, daremos una mirada al API de comunicaciones en Java (javax.comm), que provee la habilidad de hacer I/O de bajo nivel a travs de puertos serial y paralelos en una computadora. No se ir tan lejos como para decir, "Si t siempre encontraste aburrido I/O, esto es para ti!" Se dir que si no encuentras I/O interesante, probablemente no sabes tanto sobre l como deberas. I/O es el medio de comunicacin entre el software y el mundo exterior (incluyendo tanto humanos como mquinas). Java provee un poderoso y flexible conjunto de herramientas para hacer esta crucial parte del trabajo. Habiendo dicho esto, comencemos con lo bsico.

1.1 Qu es un Stream?: Un Stream (flujo) es una secuencia de bytes ordenados de un tamao indeterminado. Los streams de entrada mueven bytes de datos dentro de un programa en Java generalmente desde alguna fuente externa. Los streams (flujos) de salida mueven bytes de datos desde Java generalmente a un objetivo externo. (En casos especiales, los streams pueden adems mover bytes desde una parte de un programa Java a otro.) La entrada a un programa Java puede venir de muchas fuentes. La salida puede ir a muchos diferentes tipos de destinos. El poder de la metafora del stream (flujo) y a su vez su conversin a clases es la diferencia entre estas fuentes y destinos. Todas las entradas y salidas son simplemente tratadas como flujos.

1.1.1 De donde vienen los Streams? La primer fuente de entrada que muchos programadores encuentran es System.in. Esta es la misma cosa que stdin en lenguaje C, generalmente algn tipo de ventana de consola, probablemente la misma donde los programas en Java son lanzados. Si la salida es re direccionada de manera que el programa lea un archivo, entonces System.in es cambiado tambin. Por instancia, en Unix, el siguiente comando re direcciona stdin de forma que cuando el programa MessageServer lee desde System.in, los datos actuales vienen desde el archivo data.txt en lugar de la consola:

% java MessageServer < data.txt

La consola est adems disponible para salida a trvez del campo esttico out en la clase java.lang.System, esa es, System.out. Esto es equivalente a stdout en lenguaje C y quiz sea re direccionado en la misma forma. Finalmente, stderr est disponible como System.err. Esto es ms comunmente usado para revisar errores e imprimir mensajes de error dentro de las clausulas catch. Por ejemplo:

try { //... Hacer algo que quiz lanze una excepcin } catch (Exception e) { System.err.println(e); }

Tanto System.in como System.out son streams (flujos) de impresin, son, instancias de java.io.PrintStream. Los archivos son otra fuente comn de entrada y de destino para salida. Los streams de entrada de archivo proveen un stream de datos que inicia con el primer byte en un archivo y termina con el ltimo byte en el archivo. Los streams de salida de archivo pueden escriben datos dentro de un archivo, borrar el contenido del mismo por el inicio o aadir datos al archivo. Esto ser abarcado en el Captulo 4.

Conexiones a la red proveen streams tambin. Cuando se hace la conexin a un servidor web, FTP o algn otro, se leen los datos

enviados desde un stream de entrada conectado a ese servidor y escribe los datos en un stream de salida conectado a ese servidor. Estos streams se introducirn en el Captulo 5.

Los programas en Java por s solos producen streams. Streams de arreglos (arrays) de bytes de entrada, streams de arreglos de bytes de salida, streams de tubera de entrada y streams de tubera de salida, todos usan la metafora del stream para mover datos de una parte de un programa Java a otro. Muchos de estos se introducen en el Captulo 8.

Tal vez sea un poco sorpresivo, pero componentes de AWT (y Swing) como TextArea no producen streams. El uso aqu es bajo peticin. Dado un grupo de bytes provistos como datos, debe haber un orden fijo en estos bytes para ser ledos o escritos como un stream. De todas formas, el usuario puede cambiar el contenido del rea o campo de texto en cualquier momento, no solo al final. Por otra parte, se podra borrar el texto de enmedio del stream mientras un thread diferente est leyendo esos datos. Por lo tanto, los streams no son una buena metfora para leer datos desde los componentes de una interfaz grfica de usuario (GUI). Se puede, de todas formas, utilizar siempre los strings que estos producen para crear un stream de entrada de arreglo de bytes o un lector de strings.

1.1.2 Las clases de stream Muchas de las clases que trabajan directamente con streams son parte del paquete java.io. Las dos clases principales son java.io.InputStream y java.io.OutputStream . Estos son las clases abstractas de base para muchas diferentes subclases con ms habilidades especializadas, incluyendo:

BufferedInputStream BufferedOutputStream ByteArrayInputStream ByteArrayOutputStream DataInputStream DataOutputStream FileInputStream FileOutputStream FilterInputStream FilterOutputStream LineNumberInputStream ObjectInputStream ObjectOutputStream PipedInputStream PipedOutputStream PrintStream

PushbackInputStream SequenceInputStream StringBufferInputStream

El paquete java.util.zip contiene cuatro clases de stream de entrada que lee datos en un formato comprimido y regresarlo en un formato no comprimido y cuatro clases de streams de salida que leen datos en formato no comprimido y escribirlos en formato comprimido. Estos sern discutidos en el Captulo 9.

CheckedInputStream CheckedOutputStream DeflaterOutputStream GZIPInputStream GZIPOutputStream InflaterInputStream ZipInputStream ZipOutputStream

El paquete java.util.jar incluye dos clases de streams para leer ficheros desde archivos JAR. Estos sern adems discutidos en el Captulo 9.

JarInputStream

JarOutputStream

El paquete java.security incluye un par de clases de streams usados para calcular resmenes de mensajes:

DigestInputStream

DigestOutputStream

La JCE (Extensin de Criptografa de Java) agrega dos clases para encripcin y decripcin:

CipherInputStream

CipherOutputStream

Estos cuatro streams sern discutidos en el Captulo 10.

Finalmente, hay unas pocas clases de streams escondidas dentro del

lo paquetes de sun (Por ejemplo, sun.net.TelnetInputStream y sun.net.TelnetOutputStream). De todas formas, estas son debliberadamente ocultas del programador cliente y son generalmente presentadas como instancias de java.io.InputStream o java.io.OutputStream solamente.

1.2 Datos numricos Los streams de entrada leen bytes y los streams de salida escriben bytes. Los lectores leen caracteres y los escritores escriben caracteres. Por lo tanto, para entender entradas y salidas, primero se necesita un solido entendimiento de como Java trata con bytes, enteros, caracteres, y otros tipos de datos primitivos, y cuando y por qu uno es convertido en otro. En muchos casos, el comportamiento de Java no es obvio.

1.2.1 Datos enteros El tipo de dato fundamental entero en Java es el int (entero), de cuatro bytes, formato big-endian, complementan el entero. Un int puede tomar todos los valores entre -2,147,483,648 y 2,147,483,647. Cuando se escribe un entero literal como 7, -8345 o 3000000000 en cdigo fuente de Java, el compilador trata esa literal como un int. en el caso de 3000000000 o numeros similares muy largos para caber en un int, el compilador emite un mensaje de error citando "Desbordamiento numrico".

El tipo de dato long es de ocho bytes, formato big-endian, ambos complementan enteros con rangos desde -9,223,372,036,854,775,808 hasta 9,223,372,036,854,775,807. Las literlales long son indicadas por sufijo el numro con una L minscula o mayscula. Una L mayscula es preferida por que la l minscula es muy fcil de confundir con el numro 1 en muchas fuentes. Por ejemplo, 7L, -8345L, y 3000000000L todas son literales long de 64-bit. Hay dos tipos de dato entero ms disponibles en Java, el tipo short y el tipo byte. Los tipo short son de dos bytes, big-endian, complementan enteros con rangos desde -32,768 hasta 32,768. Son raramente usados en Java y son incluidos principalmente para compatibilidad con C.

Los bytes de todas formas, son muy usados en Java. En particular son usados en I/O. Un byte es de ocho bits, complementa interos con rangos desde -128 hasta 127. Notese que como todos los tipos de datos numricos en Java, un byte es firmado. El valor maximo de un byte es 127. 128, 129 y los dems hasta 255 son valores no legales para bytes. 1.5 La omnipresente IOException Como van las operaciones de computadoras, las entradas y salidas son poco confiables, estn sujetas a problemas completamente fuera del control de los programadores. Los discos pueden desarrollar malos sectores mientras un archivo est siendo ledo; los trabajadores de construccin dejan caer excavadoras a travs de los cables que conectan a tu WAN (Wide Area Network); los usuarios cancelan inesperadamente su entrada; equipos de reparacin de telefono apagan tu lnea de modem mientras tratan de reparar la de alguien ms. A causa de estos potenciales problemas y muchos ms, casi cualquier mtodo que realize entradas o salidas es declarado para lanzar una IOException. IOException es una excepcin controlada, as que o bien declara que tu mtodo la lanza o adjunta la llamada que puede lanzarla en un bloque try/catch. Las nicas excepciones reales son las clases PrintStream y PrintWriter. Por que sera inconveniente envolver en un bloque try/catch al rededor de cada llamada a System.out.println(), Sun decidi que tener a PrintStream (y ms tarde a PrintWriter) atrapando y "comiendo" cualquier excepcin que lanze dentro de un mtodo print() o println(). Si quieres revisar la excepciones dentro de un mtodo print() o println(), puedes llamar a checkError():

public boolean checkError()

El mtodo checkError() regresa true si una excepcin ha ocurrido en ese flujo de impresin (print stream), falso si no ha ocurrido ninguna. Slo te dice que un error ocurri. No te dice que tipo de error ocurri. Si necesitas saber ms acerca del error, tendras que usar un diferente flujo de salida (output stream) o clase escritora (writer). IOException tiene muchas subclases15 en java.ioy mtodos que muchas veces lanzan una excepcin ms especifca que es una

subclase de IOException. (De todas formas, los mtodos usualmente declaran que ellos lanzan una IOException.) Aqu estn las subclases de IOException que encontrars en el paquete java.io: CharConversionException EOFException FileNotFoundException InterruptedIOException InvalidClassException InvalidObjectException NotActiveException NotSerializableException ObjectStreamException OptionalDataException StreamCorruptedException SyncFailedException UTFDataFormatException UnsupportedEncodingException WriteAbortedException Hay un nmero de subclases de IOException dispersas en los otros paquetes, particularmente java.util.zip (DataFormatException y ZipException) y java.net (BindException, ConnectException, MafFormedURLException, NoRouteHostException, ProtocolException, SocketException, UnknownHostException, y UnknownServiceException). La clase java.io.IOException no declara ningn mtodo pblico o campos de importanciaSlo los dos constructores usuales que encuentras en muchas clases de excepcin:

public IOException() public IOException(String message)

El primer constructor crea una IOException con un mensaje vaco. El segundo provee ms detalles acerca de lo que fu mal. Por su puesto, IOException tiene los mtodos habituales heredados por toda clase de excepciones como lo es toString() y printStackTrace().

1.6 La consola: System.out, System.in, System.err La consola es el destino por default para salida escrita a System.out o System.err y la fuente por default para System.in. En muchas plataformas la consola es el entorno de la lnea de comandos desde donde el programa Java fu inicialmente lanzado, tal vez un xterm o una ventana DOS shell. La palabra consola es algo de nombre equivocado, ya que en los sistemas Unix la consola se refiere a un muy especifco caparazn de lnea de comandos, en lugar de ser un termino generico para los caparazones de lneas de comandos en su totalidad.

Muchos conceptos errneos comunes acerca de I/O ocurren por que la mayor parte de de los programadores tienen su primera exposicin a I/O a travs de la consola. La consola es conveniente para pruebas rpidas y "ejemplos de juguete" comnmente encontrados en libros de texto, se usar la consola para eso en este libro, pero es realmente una muy inusual fuente de entrada y destino para salida, y los buenos programas en Java la evitan. Mientras la consola hace convenientes ejemplos en textos de programacin como este, son una horrible interfaz de usuario y realmente tienen un lugar pequeo en los programas modernos. Los usuarios estn ms cmodos con una bien definida interfaz grfica de usuario. Adems, la consola es poco confiable a travs de las plataformas. La Mac, por ejemplo, no tiene una consola nativa. Tiempo de ejecucin Macintosh para Java 2 y posteriores tienen una ventana de consola que trabaja slo para salida, no para entrada; eso significa que, System.out trabaja pero System.in no lo hace.

1.6.2 System.out: Especficamente, System.out es el campo de salida esttica de las clases java.lang.System. Es una instancia de java.io.PrintStream, una subclase de java.io.OutputStream. System.out corresponde a stdout en Unix o C. Normalmente, la salida enviada a System.out aparece en la consola. Como una regla general, la consola convierte los datos numricos de bytes de System.out y los enva en texto ASCII o ISO Latin-1.

1.6.3 System.err: stderr es un archivo de puntero de stdout, pero a menudo significa la misma cosa. Generalmente, stderr y stdout ambos envan datos a la consola, sea lo que sea. De cualquier manera, stdout y stderr pueden ser re direccionados a diferentes lugares, Por instancia, la salida puede ser re direccionada a un archivo mientras los mensajes de error siguen apareciendo en la consola. System.err es una versin de Java del stderr. Como System.out, System.err es una instancia de java.io.PrintStream, una subclase de java.io.PrintStream, una subclase de java.io.OutputStream . System.err es ms comnmente usado dentro de la clusula catch de un bloque try/catch.

Los programas terminados no deberan tener mucha necesidad de System.err, pero es til mientras ests en la fase de depuracin.

1.6.4 System.in: System.in es el stream de entrada conectado a la consola, tanto como System.out es el stream de salida conectado a la consola. En los trminos de Unix o C, System.in es stdin y puede ser re direccionado desde un Shell en la misma manera. System.in es el campo de entrada esttico de la clase java.lang.System . Es una instancia de java.io.InputStream, al menos en lo que se documenta. Pasado lo que est documentado, System.in es realmente un java.io.BufferedInputStream. BufferedInputStream no declara ningn nuevo mtodo, slo sobrescribe los que ya estn declarados en java.io.InputStream.

1.7 Revisiones de seguridad en I/O: Uno de los miedos originales sobre descargar contenido ejecutable como applets de Internet era que un applet hostil pudiera borrar tu disco duro, o leer tus archivos. Nada parece cambiar desde que Java fue introducido. Esto es por lo cual los applets Java corren bajo el control de un administrador de seguridad (security manager) que revisa cada operacin que realiza un applet para prevenir potenciales actos hostiles. El administrador de seguridad es particularmente cuidadoso sobre las operaciones de entrada y salida (I/O). En su mayora, las revisiones estn relacionadas a estas preguntas:

Puede un applet leer un archivo? Puede un applet escribir un archivo? Puede un applet borrar un archivo? Puede un applet determinar donde existe un archivo? Puede un applet hacer una conexin de red a un host en

particular? Puede un applet aceptar una conexin proveniente de un host en particular?

La respuesta para todas estas preguntas es: "No, no puede." Una respuesta un poco ms elaborada podra especificar un pequeo nmero de excepciones. Los applets pueden hacer conexiones de red al host de donde vienen; los applets pueden leer un nmero pequeo de archivos especficos que contienen informacin acerca del entorno de Java; y los applets firmados quiz a veces corran sin sas restricciones. Pero para casi todos los propsitos prcticos, la respuesta es casi siempre no. Por estas cuestiones de seguridad, hay que ser cuidadosos cuando usen fragmentos de este texto en un applet. Todo lo mostrado aqu trabaja en una aplicacin, pero cuando corre en un applet, quiz falle con una SecurityException. No es siempre obvio donde un mtodo particular o clase va a causar problemas. El mtodo write() de BufferedOutputStream, por instancia, es completamente seguro cuando el destino final es un arreglo de bytes. De cualquier forma, el mismo mtodo write() va a lanzar una excepcin cuando el destino es un archivo. Un intento de abrir una conexin a un servidor web quiz tenga xito o falle dependiendo de si el servidor web al que ests conectando es el mismo de donde viene el applet o no. Hay muy poca entrada y salida (I/O) que puede hacerse desde un applet sin tener conflicto con el administrador de seguridad. El problema quiz no siempre sea obvio (No todos los exploradores web reportan apropiadamente las excepciones de seguridad) pero est ah. Si puedes hacer que un applet trabaje cuando est corriendo como una aplicacin por s mismo y no puedes ponerlo a trabajar dentro de un explorador web, es casi seguro que el problema es un conflicto con el administrador de seguridad del explorador.

Captulo 2. Streams de salida


2.1 La clase OutputStream:

La clase java.io.OutputStream declara los 3 bsicos mtodos que necesitas para escribir bytes de datos en un stream. Adems tiene mtodos para el cerrado y lavado de streams.

public abstract void write(int b) throws IOException public void write(byte[] data) throws IOException public void write(byte[] data, int offset, int length) throws IOException public void flush() throws IOException public void close() throws IOException

OutputStream es una clase abstracta. Las subclases proveen implementaciones del mtodo abstracto write(int b). Las subclases adems reemplazan los 4 mtodos no abstractos. Por ejemplo, la clase FileOutputStream reemplaza los 5 mtodos con mtodos nativos que saben cmo escribir bytes a archivos en plataforma husped. Aunque OutputStream es abstracto, muchas veces slo necesitas saber que el objeto que tienes es un OutputStream; la subclase ms especfica de OutputStream est escondida. Por ejemplo el mtodo getOutputStream() de java.net.URLConnection tiene la firma:

public OutputStream getOutputStream() throws IOException

Dependiendo del tipo de URL asociada con este objeto URLConnection, la clase actual que es regresada quiz sea una sun.net.TelnetOutputStream , una sun.net.smtp.SmtpPrintStream , una sun.net.www.http.KeepAliveStream, o algo ms completamente. Todo lo que t sabes cmo programador (y todo lo que necesitas saber), es que el objeto regresado es de hecho alguna instancia de OutputStream. se es el porqu de las clases que manejan detallados y particulares tipos de conexin estn escondidos dentro de los paquetes de sun. Por lo tanto, aun cuando trabajas con subclases de aquellos tipos que no conoces, an necesitas poder usar los mtodos heredados de OutputStream. Y desde que hay mtodos heredados que no estn incluidos en la documentacin Online, es importante recordar que estn ah. Por ejemplo, la clase java.io.DataOutputStream no declara un mtodo close(), pero an puede llamar al que hereda desde su

superclase.

2.2 Escribiendo Bytes a Streams de salida: El mtodo fundamental de la clase OutputStream es write():

public abstract void write(int b) throws IOException

Este mtodo escribe un solo byte sin firmar de dato cuyo valor debera estar entre y 255. Si se pasa un nmero mayor que 255 o menor que cero, es reducido al mdulo 256 antes de empezar a escribir. Ejemplo 2.1, AsciiChart, es un simple programa que escribe los caracteres ASCII imprimibles (del 32 al 126) en la consola. La consola interpreta los valores numricos como caracteres ASCII, (no como nmeros). Esta es una caracterstica de la consola, no de la clase OutputStream o de la clase especfica de la cual System.out es una instancia. El mtodo write() simplemente enva un particular patrn de bit a un particular stream de salida. Como ese patrn de bit es interpretado depende de lo que est conectado al otro lado del stream. Ejemplo 2.1. El programa AsciiChart import java.io.*; public class AsciiChart { public static void main(String[] args) { for (int i = 32; i < 127; i++) { System.out.write(i); // rompe lnea cada ocho caracteres. if (i % 8 == 7) System.out.write('\n'); else System.out.write('\t'); } System.out.write('\n'); }

Nota el uso de las literales char '\t' y '\n'. El compilador convierte estos a los nmeros 9 y 10 respectivamente. Cuando estos nmeros son escritos en la consola, la consola interpreta estos nmeros como un tab y un salto de lnea, respectivamente. El mismo efecto puede ser logrado escribiendo la clusula if as:

if (i % 8 == 7) System.out.write(10); else System.out.write(9);

Aqu est la salida del programa:

% java AsciiChart ! ( 0 8 @ H P X ` h p x % i q y " ) 1 9 A I Q Y a j r z # * 2 : B J R Z b $ + 3 ; C K S [ c k s { l t | % , 4 < D L T \ d & 5 = E M U ] e m u } ' . 6 > F N V ^ f n v ~ / 7 ? G O W _ g o w

El mtodo write() puede lanzar una IOException, as que necesitaremos

envolver la mayor parte de las llamadas a este mtodo en un bloque try/catch, o declarar que tu propio mtodo lanza una IOException. Por ejemplo:

try { for (int i = 32; i <= 127; i++) out.write(i); } catch (IOException e) { System.err.println(e); }

Los lectores astutos, se habrn dado cuenta de que el Ejemplo 2.1 actualmente no atrapa ninguna IOException. La clase PrintStream, de la cual System.out es una instancia, reemplaza el mtodo write() con una variante que no lanza una IOException.

2.3 Escribiendo arreglos de Bytes Es muchas veces ms rpido escribir largos trozos de datos que escribirlos byte por byte. Dos variantes sobrecargadas del mtodo write() hacen esto:

public void write(byte[] data) throws IOException public void write(byte[] data, int offset, int length) throws IOException

La primera variante escribe el arreglo de bytes entero. La segunda escribe solo el sub-array de data iniciando en offset y continuando por length bytes. Por ejemplo, el siguiente fragmento de cdigo lanza los bytes en una cadena de caracteres (string) haca System.out:

String s = "How are streams treating you?"; byte[] data = s.getBytes(); System.out.write(data);

Inversamente, quiz tengas problemas de rendimiento si intentas escribir muchos datos a la vez. El punto de cambio exacto depende en la destinacin eventual de los datos. Ejemplo 2.2 es un simple programa que construye un arreglo de datos llenados con caracteres ASCII, entonces los lanza a la consola en una llamada a write(). Ejemplo 2.2. El programa AsciiArray

import java.io.*; public class AsciiArray { public static void main(String[] args) { byte[] b = new byte[(127-31)*2]; int index = 0; for (int i = 32; i < 127; i++) { b[index++] = (byte) i; // Break line after every eight characters. if (i % 8 == 7) b[index++] = (byte) '\n'; else b[index++] = (byte) '\t'; } b[index++] = (byte) '\n'; try { System.out.write(b); } catch (IOException e) { System.err.println(e); } } }

La salida es la misma que en el Ejemplo 2.1. Por la naturaleza de la

consola, este programa en particular probablemente no sea ms rpido que en el Ejemplo 2.1, pero ciertamente podra serlo si se estuvieran escribiendo datos a un archivo en lugar de a la consola. La diferencia en rendimiento entre escribir un arreglo de bytes en una sola llamada a write() y escribir el mismo arreglo invocando write() una vez por cada componente del arreglo puede ser fcilmente un factor de cien o ms.

2.4 Limpiando y cerrando Streams de salida Muchos streams de salida escriben buffer para mejorar el rendimiento. En lugar de enviar cada byte a su destino como est escrito, los bytes son acumulados in una memoria buffer que van en tamaos desde varios bytes hasta varios cientos de bytes. Cuando el buffer se llena, todos los datos son enviados a la vez. El mtodo flush() fuerza a los datos a ser escritos tanto si el buffer est lleno como si no:

public void flush() throws IOException

Esto no es lo mismo que cualquier bfer realizado por el sistema operativo o el hardware. Estos bferes no van a ser vaciados por una llamada a flush(). (Entonces el mtodo sync() en la clase FileDescriptor, discutido en el Captulo 12, puede a veces ser usado para vaciar estos bfers.) Por ejemplo, asumiendo que 'out' es un OutputStream de alguna clase, se debera llamar a out.flush() para vaciar los bfers. Si t solo usas un stream por un tiempo corto, no es necesario limpiarlo explcitamente. Debera ser limpiado cuando se cierra el stream (esto es trabajo del GarbageCollector hasta donde s). Esto debera pasar cuando el programa sale (GarbageCollector) o cuando explcitamente se invoca el mtodo close():

public void close() throws IOException

Por ejemplo, asumiendo de nuevo que 'out' es un OutputStream de algn tipo, llamando a out.close() cierra el stream y explctamente lo

limpia. Una vez que se haya cerrado un stream de salida, no se puede escribir ms en l. Intentar hacerlo lanzar una IOException. De nuevo, System.out es una excepcin parcial porque es un PrintStream , todas las excepciones que lanza son 'comidas'. Una vez que se cierra System.out, no se puede escribir en l, pero tratando no lanzar ninguna excepcin. De cualquier manera, la salida no aparecer en la consola. Slo se necesita limpiar un stream de salida explcitamente si se quiere asegurar que los datos fueron enviados antes de terminar con el stream. Por ejemplo, un programa que enva muchos datos en una red peridicamente debe limpiar despus de que cada envo de datos sea escrito al stream. La limpieza de streams es adems importante muchas veces cuando se depura un programa con errores. Todos los streams hacen limpieza automticamente cuando sus bfers se llenan, y todos los streams deberan ser limpiados cuando un programa termina normalmente. Si un programa termina anormalmente, sin embargo, los bfers quiz no se limpien. En este caso, si no se hace una llamada explcita a flush() despus de cada escritura, no se puede estar seguro de que los datos que aparecen en la salida indican el punto en el que el programa fall. De hecho, el programa quiz haya continuado trabajando pasado el punto donde hubo un error. System.out, System.err y algunos (pero no todos) otros streams de impresin automticamente se limpian despus de cada llamada a println() y despus de cada vez que aparece en la cadena un carcter de nueva lnea ('\n') se escribe. Si el auto-flushing est habilitado puede ser puesto en el constructor de PrintStream.

2.5 Subclases de OutputStream OutputStream es una clase abstracta que principalmente describe las operaciones disponibles con cualquier objeto OutputStream en particular. Las clases especficas saben cmo escribir bytes en destinos particulares. Por ejemplo, un FileOutputStream usa cdigo nativo para escribir datos en archivos. Un ByteArrayOutputStream usa Java puro para escribir su salida en un potencial arreglo de bytes expansivo. Recordemos que hay tres variantes sobrecargadas del mtodo write() en OutputStream, uno abstracto, y dos en concreto:

public abstract void write(int b) throws IOException public void write(byte[] data) throws IOException public void write(byte[] data, int offset, int length) throws IOException

Las subclases deben implementar el mtodo abstracto write(int b). Muchas veces eligen reemplazar la tercer variante, write(byte[] data, int offset, int length), por razones de rendimiento. La implementacin de las tres versiones argumentadas del mtodo write() en OutputStream simplemente invoca write(int b) repetidamente; de esta forma:

public void write(byte[] data, int offset, int length) throws IOException { for (int i = offset; i < offset+length; i++) write(data[i]); }

Muchas subclases pueden proveer implementaciones ms eficaces de este mtodo. El argumento de la variante write() simplemente invoca write(data, 0, data.length); Si la variante de 3 argumentos ha sido reemplazada, este mtodo tendr un rendimiento razonablemente bueno. De todas formas, unas pocas subclases quiz lo reemplacen de todas formas. Ejemplo 2.3 es un simple programa llamado NullOutputStream que imita el comportamiento de /dev/null en los sistemas operativos Unix. Los datos escritos en un stream de salida se pierden.

Ejemplo 2.3. La clase NullOutputStream package com.macfaq.io; import java.io.*; public class NullOutputStream extends OutputStream { public void write(int b) { } public void write(byte[] data) { }

public void write(byte[] data, int offset, int length) { } }

Re direccionando System.out y System.err a un stream de salida nulo en la versin de lanzamiento (versin alfa) de tu programa, puedes deshabilitar cualquier mensaje de depuracin que quiz se hayan colado en el aseguramiento de calidad. Por ejemplo:

OutputStream out = new NullOutputStream(); PrintStream ps = new PrintStream(out); System.setOut(ps); System.setErr(ps);

2.6 Una interfaz grfica de usuario para streams de salida Como un ejemplo til, se va a mostrar una subclase de java.awt.TextArea que puede ser conectado a un stream de salida. Como los datos son escritos en el stream, es aadido al rea de texto en el set de caracteres por default (generalmente ISO Latin-1). (Esto no es ideal. Desde que las reas de texto contienen textos, un escritor debera ser una mejor fuente para estos datos; en los siguientes captulos se expandir esta clase para usar un escritor en cambio. Por ahora esto hace un buen ejemplo.) Esta subclase se muestra en el Ejemplo 2.4. El actual stream de salida est contenido en una clase interna dentro de la clase StreamedTextArea. Cada componente StreamedTextArea contiene un objeto TextAreaOutputStream en su campo theOutput. Los programadores cliente accedan a este objeto por el mtodo getOutputStream() de la clase StreamedTextArea . La clase StreamedTextArea tiene cinco constructores sobrecargados que imitan los 5 constructores en la clase java.awt.TextArea , cada que se toma una diferente combinacin de texto, filas, columnas e informacin de barras de desplazamiento. Los primeros cuatro constructores simplemente pasan sus argumentos y defaults convenientes al quinto constructor usando this(). El quinto constructor llama al constructor de la superclase ms general, entonces llama a setEditable(false) para asegurarse que el usuario no cambie el texto mientras la salida est

haciendo un flujo en ella. Se ha escogido no reemplazar ningn mtodo en la superclase TextArea. De todas formas, quizs quieras hacerlo si sientes a necesidad de cambiar las habilidades normales de un rea de texto. Por ejemplo, se podra incluir un mtodo append() que no haga nada as que los datos slo pueden ser movidos en el rea texto por el stream de salida a un mtodo setEditable() que no permita al programador cliente hacer esta rea editable.

Ejemplo 2.4. El componente StreamedTextArea

package com.macfaq.awt; import java.awt.*; import java.io.*; public class StreamedTextArea extends TextArea { OutputStream theOutput = new TextAreaOutputStream(); public StreamedTextArea() { this("", 0, 0, SCROLLBARS_BOTH); } public StreamedTextArea(String text) { this(text, 0, 0, SCROLLBARS_BOTH); } public StreamedTextArea(int rows, int columns) { this("", rows, columns, SCROLLBARS_BOTH); }

public StreamedTextArea(String text, int rows, int columns) { this(text, rows, columns, SCROLLBARS_BOTH); }

public StreamedTextArea(String text, int rows, int columns, int scrollbars) { super(text, rows, columns, scrollbars); setEditable(false); } public OutputStream getOutputStream() { return theOutput; } class TextAreaOutputStream extends OutputStream { public synchronized void write(int b) { // Recordar que el entero debera ser realmente solo un byte b &= 0x000000FF; //Se debe convertir el byte a char para poder aadirlo char c = (char) b; append(String.valueOf(c)); } public synchronized void write(byte[] data, int offset, int length) { append(new String(data, offset, length)); } } }

La clase interna de TextAreaOutputStream es muy simple. sta extiende a OutputStream y ellos deben implementar el mtodo abstracto write(). Adems reemplaza el mtodo de arreglo primario write() para proveer una implementacin ms eficiente. Para usar esta clase, simplemente agregas una instancia de ella a un contenedor como un applet o una ventana, tanto como se agrega un rea de texto regular. Lo siguiente es invocar a su mtodo getOutputStream() para tener una referencia al stream de salida para el rea, entonces hay

que usar el usual mtodo write() para escribir en el rea de texto. Muchas veces estos pasos van a tomar lugar en diferentes tiempos y en diferentes mtodos.

Captulo 3. Streams de entrada

3.1 La clase InputStream La clase java.io.InputStream es la superclase abstracta para todos los streams de entrada. Declara en ella los tres mtodos bsicos necesarios para leer bytes de datos desde un stream. Adems tiene mtodos para cerrar y limpiar streams, revisa cuantos datos hay disponibles para leer, salta la entrada, marca una posicin en un stream y vuelve a sa posicin, y determina donde las marcas y los regresos a sas marcas son posibles.

public abstract int read() throws IOException public int read(byte[] data) throws IOException public int read(byte[] data, int offset, int length) throws IOException public long skip(long n) throws IOException public int available() throws IOException public void close() throws IOException public synchronized void mark(int readlimit) public synchronized void reset() throws IOException public boolean markSupported()

3.2 El mtodo read() El mtodo fundamental de la clase InputStream es read(), que lee un

byte de dato sin firmar y devuelve el valor entero del byte sin firmar. Este es un nmero entre 255:

public abstract int read() throws IOException

El siguiente cdigo lee 10 bytes desde el stream de entrada System.in y los almacena en el arreglo de datos entero:

int[] data = new int[10]; for (int i = 0; i < data.length; i++) { data[i] = System.in.read(); }

Nota que casi todos los read() leen un byte, y regresan un entero. Si t en cambio quieres almacenar los bytes desordenados, puedes convertir el entero a byte. Por ejemplo:

byte[] b = new byte[10]; for (int i = 0; i < b.length; i++) { b[i] = (byte) System.in.read(); }

Por su puesto, esto produce un byte firmado en vez del byte sin firmar regresado por el mtodo read() (eso es, un byte en el rango de -128 a 127 en vez de hasta 255). Mientras tengas claro en mente y cdigo sobre donde se est usando datos firmados o sin firmar, no habr ningn problema. Los bytes firmados pueden ser convertidos de vuelta a enteros en el rango de 255 de esta forma:

int i = (b >= 0) ? b : 256 + b;

Cuando llamas a read(), adems se tendr que atrapar la IOException que quiz lanz. Como se ha observado, la entrada/salida (I/O) estn sujetas a problemas fuera del control del programador como son: fallas de disco, cables de red rotos, entre otros. Aun as, virtualmente cualquier mtodo de entrada/salida (I/O) puede lanzar una IOException, y read() no es la excepcin. No habr una IOException si read() encuentra el fin del stream de entrada; en ste caso, regresa -1. Usa esto como 'bandera' para ver el fin del stream. El siguiente cdigo muestra como atrapar la IOException y probar el fin del stream:

try { int[] data = new int[10]; for (int i = 0; i < data.length; i++) { int datum = System.in.read(); if (datum == -1) break; data[i] = datum; } } catch (IOException e) {System.err.println("Couldn't read from System.in!");}

El mtodo read() espera o se bloquea hasta que un byte de datos est disponible y listo para ser ledo. La entrada y salida (I/O) puede ser lenta, as que si tu programa est haciendo cualquier otra cosa de importancia, se debera poner la entrada/salida (I/O) en su propio thread. read() es declarado abstracto; por lo tanto, InputStream es abstracto. Por consiguiente, no es posible instanciar un InputStream directamente; siempre se debe trabajar con una de sus subclases en concreto. El Ejemplo 3.1 es un programa que lee datos desde System.in, tambin imprime el valor numrico de cada byte en la consola usando System.out.println(). Este programa en particular, pudo haber sido ms simple, se pudo haber puesto la lgica en el mtodo main() sin ningn problema. De todas formas, este ejemplo es la base de una utilidad

para archivos que se ha desarrollado a travs del libro, y, por lo tanto, se quiere un diseo flexible desde el inicio.

Ejemplo 3.1. La clase StreamPrinter

package com.macfaq.io; import java.io.*; public class StreamPrinter { InputStream theInput; public static void main(String[] args) { StreamPrinter sr = new StreamPrinter(System.in); sr.print(); } public StreamPrinter(InputStream in) { theInput = in; }

public void print() { try { while (true) { int datum = theInput.read(); if (datum == -1) break; System.out.println(datum); } } catch (IOException e) {System.err.println("Couldn't read from System.in!");}

} }

3.3 Leyendo trozos de datos desde un Stream La entrada y salida (I/O) son muchas veces "cuellos de botella" en el rendimiento de un programa. Leer desde o imprimir datos al disco puede ser cientos de veces ms lento que leer o escribir a la memoria; las conexiones a la red y entradas de usuario son an ms lentas. Mientras las capacidades y velocidades de los discos se incrementan con el tiempo, nunca estn en paz con las velocidades de un CPU. Por lo tanto, es importante minimizar el nmero de lecturas y escrituras que un programa realiza. Todos los streams de entrada tienen mtodos read() sobrecargados que leen trozos de datos continuos en un arreglo de bytes. La primera variante trata de leer suficientes datos para llenar el arreglo de datos. La segunda variante trata de leer length bytes de datos iniciando en la posicin offset en el arreglo de datos. Ninguno de estos mtodos garantiza leer tantos bytes como se quiere. Ambos mtodos regresan el nmero de bytes que leen, o -1 en el fin del stream.

public int read(byte[] data) throws IOException public int read(byte[] data, int offset, int length) throws IOException

Las implementaciones por default de estos mtodos en la clase java.io.InputStream simplemente llama al mtodo bsico read() suficientes veces como para llenar el arreglo o sub-arreglo pedido. Por lo tanto, leer 10 bytes de datos toma lo mismo que 10 veces la lectura de un byte de datos. De todas formas, muchas sub-clases de InputStream reemplazan estos mtodos con otros ms eficientes, tal vez nativos (mtodos usados para interactuar con otros lenguajes), que leen los datos desde la fuente subyacente como un bloque de instrucciones. Por ejemplo, intentar leer 10 bytes desde System.in, se podra escribir el siguiente cdigo:

try {

byte[] b = new byte[10]; System.in.read(b); } catch (IOException e) {System.err.println("Couldn't read from System.in!");}

Las lecturas de datos no siempre tienen xito en obtener tantos bytes como se quiere. Por otro lado, no hay nada que te detenga por tratar de leer ms datos en el arreglo de los que cabran. Si se leen ms datos de los que el arreglo puede sostener, una ArrayIndexOutOfBoundsException ser lanzada (yo en lo personal he trabajado mucho con esta excepcin). Por ejemplo, el siguiente cdigo hace un bucle repetitivo hasta que se llene el arreglo o se 'vea' el fin del stream:

try { byte[] b = new byte[100]; int offset = 0; while (offset < b.length) { int bytesRead = System.in.read(b, offset, b.length - offset); if (bytesRead == -1) break; // fin del stream offset += bytesRead; } } catch (IOException e) {System.err.println("Couldn't read from System.in!");}

3.4 Contando los bytes disponibles Es a veces conveniente saber cuntos bytes estn disponibles para

leer antes de que se intente leerlos. La clase InputStream tiene un mtodo available() que 'te dice' cuantos bytes se pueden leer sin bloquearse. Regresa si no hay datos disponibles para leer.

public int available() throws IOException

Por ejemplo: try { byte[] b = new byte[100]; int offset = 0; while (offset < b.length) { int a = System.in.available(); int bytesRead = System.in.read(b, offset, a); if (bytesRead == -1) break; // fin del stream offset += bytesRead; } catch (IOException e) {System.err.println("Couldn't read from System.in!");}

Hay un error potencial en este cdigo. Quiz haya ms bytes disponibles de los que tiene el arreglo de espacio para guardarlos. un modismo comn es dimensionar el arreglo de acuerdo al nmero que available() regresa, de esta forma:

try { byte[] b = new byte[System.in.available()]; System.in.read(b); } catch (IOException e) {System.err.println("Couldn't read from

System.in!");}

Esto trabaja bien si solo se va a realizar una sola lectura. Para lecturas mltiples de todas formas, los "gastos generales" de crear mltiples arreglos son excesivos. Probablemente haya que reusar el arreglo y solo crear un nuevo arreglo si ms bytes estn disponibles de los que cabran en el arreglo. El mtodo available() en java.io.InputStream siempre regresa 0. Las subclases deben reemplazarlo, pero he visto algunas que no lo hacen. Quiz sea posible leer ms bytes desde el stream subyacente sin el bloqueo que available() sugiere; pero no se puede garantizar que se puede. Si esto es una preocupacin, se puede poner la entrada en un thread separado de esa forma bloquear la salida no bloqueara el resto del programa.

3.5 Saltando bytes Aunque se pueda leer solo de un stream e ignorar los bytes ledos, Java provee un mtodo skip() que salta sobre cierto nmero de bytes en la entrada:

public long skip(long bytesToSkip) throws IOException

El argumento a skip() es el nmero de bytes a saltar. El valor que regresa es el nmero de bytes actualmente saltados que quiz sean menos que bytesToSkip. -1 es devuelto si se encuentra el fin del stream. Ambos el argumento y el valor que regresa son de tipo long, dejando a skip() manejar streams de entrada extremadamente largos. Saltar datos es mucho ms rpido que leer y descartar los datos que no quieres. Por ejemplo, cuando un stream de entrada est adjunto a un archivo, saltando bytes solo requiere que un entero, llamado el puntero del archivo sea cambiado, mientras que la lectura implica copiar bytes desde el disco a la memoria. Por ejemplo, para saltar los siguientes 80 bytes del stream de entrada in:

try {

long bytesSkipped = 0; long bytesToSkip = 80; while (bytesSkipped < bytesToSkip) { long n = in.skip(bytesToSkip - bytesSkipped); if (n == -1) break; bytesSkipped += n; } } catch (IOException e) {System.err.println(e);}

3.6 Cerrando streams de entrada Cuando se est trabajando con un stream, al final, debe cerrarse. Esto permite al sistema operativo liberar cualquier fuente asociada con el stream; lo que sas fuentes son exactamente depende de la plataforma y varia con el tipo de stream. De todas formas, los sistemas solo tienen fuentes limitadas. Por ejemplo, en muchos sistemas operativos de computadoras personales, no ms de unos cientos archivos pueden abrirse a la vez. Los sistemas operativos de multiusuario tiene enormes lmites, pero lmites de todas formas. Para cerrar un stream, se invoca a su mtodo close():

public void close() throws IOException

No todos los streams necesitan cerrarse, System.in generalmente no necesita ser cerrado, por ejemplo. De todas formas, streams asociados con archivos y conexiones a la red deberan ser siempre cerrados cuando se ha acabado con ellos. Por ejemplo:

try { URL u = new URL("http://www.javasoft.com/");

InputStream in = u.openStream(); // Leyendo desde el stream... in.close(); } catch (IOException e) {System.err.println(e);}

Una vez que has cerrado un stream de entrada, no es posible leer desde l. Intentarlo lanzar una IOException.

3.7 Marcando y reinicializando Es muchas veces til tener la posibilidad de leer unos cuantos bytes y entonces regresar volver a leerlos. Por ejemplo, en un compilador Java, no se sabe a ciencia cierta si se est leyendo el smbolo <, <<, o <<= hasta que hayas ledo unos cuantos caracteres. Sera muy til tener la posibilidad de volver y leer de nuevo el smbolo una vez que se sabe que smbolo se leer. El diseo del compilador y otros problemas de traduccin dan muchos ms ejemplos, y esto necesita ocurrir en otros dominios tambin. Algunos (pero no todos) los streams de entrada te permiten marcar una posicin particular en el stream y entonces regresar a ella. Tres mtodos en la clase java.io.InputStream manejan marcado y reinicio:

public synchronized void mark(int readLimit) public synchronized void reset() throws IOException public boolean markSupported()

El mtodo booleano markSupported() regresa true si este stream soporta el marcado y false si no. Si el marcado no es soportado, reset() lanza una IOException y mark() no har nada. Asumiendo que el stream soporta el marcado, el mtodo mark() pone un marcador en la posicin actual del stream. Es posible regresar el stream a esta posicin ms tarde con reset() mientras no se hayan ledo ms de readLimit bytes. Puede haber solo una marca en el stream a la vez. Marcar una segunda locacin elimina la primera marca.

Las nicas dos clases de streams de entrada en java.io que siempre soportan el marcado con BufferedInputStream (del cual System.in es una instancia) y ByteArrayInputStream. De todas formas, otros streams de entrada, como DataInputStream, quiz soporten el marcado si estn encadenados a una stream de entrada regulado primero.

3.8 Subclases de InputStream Las subclases inmediatas de InputStream deben proveer una implementacin del mtodo abstracto read(). Ellas adems deben reemplazar algunos de los mtodos no abstractos. Por ejemplo, el mtodo default markSupported() regresa false, mark() no hace nada, y reset() lanza una IOException. Cualquier clase que permita el marcado e inicializacin deben reemplazar estos tres mtodos. Por otra parte, ellos quiz quieran reemplazar los mtodos que realizan funciones como skip() y los otros dos mtodos read() para proveer implementaciones ms eficientes. El Ejemplo 3.2 es una simple clase llamada RandomInputStream que "lee" bytes de datos al azar. Esto provee una fuente til de datos ilimitados que pueden ser usados para pruebas. Un objeto java.util.Random provee los datos.

Ejemplo 3.2 La clase RandomInputStream package com.macfaq.io; import java.util.*; import java.io.*; public class RandomInputStream extends InputStream { private transient Random generator = new Random(); public int read() { int result = generator.nextInt() % 256; if (result < 0) result = -result; return result; } public int read(byte[] data, int offset, int length) throws IOException { byte[] temp = new byte[length]; generator.nextBytes(temp); System.arraycopy(temp, 0, data, offset, length); return length;

} public int read(byte[] data) throws IOException { generator.nextBytes(data); return data.length; } public long skip(long bytesToSkip) throws IOException { // Es todo aleatorio as que skip no tiene efecto return bytesToSkip; } }

El mtodo de no-argumento read() regresa un entero aleatorio en el rango de un byte sin firmar (de 0 a 255). Los otros dos mtodos read() llenan una parte especfica de un arreglo con bytes aleatorios. Ellos regresan los nmeros de bytes ledos (en este caso el nmero de bytes creados).

3.9 Un eficiente copiador de stream Como un ejemplo til tanto de streams de entrada y salida, en el Ejemplo 3.3 se presentar una clase StreamCopier que copia datos entre dos streams tan rpido como sea posible. (Se reusar esta clase en captulos posteriores.) Este mtodo lee desde el stream de entrada y escribe en el stream de salida hasta que el stream de entrada est 'exhausto'. Un bfer de 256 bytes es usado para tratar de hacer las lecturas ms eficientes. Un mtodo main() provee una simple prueba para esta clase leyendo desde System.in y copiando a System.out.

Ejemplo 3.3 La clase StreamCopier

package com.macfaq.io; import java.io.*; public class StreamCopier { public static void main(String[] args) { try { } catch (IOException e) {System.err.println(e);} } public static void copy(InputStream in, OutputStream out) throws IOException {

//No permite a otros threads leer de la entrada o escribir //a la salida mientras el copiado est tomando lugar synchronized (in) { synchronized (out) { byte[] buffer = new byte[256]; while (true) { int bytesRead = in.read(buffer); if (bytesRead == -1) break; out.write(buffer, 0, bytesRead); } } } } } Aqu hay una simple prueba corriendo: D:\JAVA\ioexamples\03>java com.macfaq.io.StreamCopier this is a test this is a test 0987654321 0987654321 ^Z

La entrada no fue 'alimentada' a la consola (DOS prompt) al StreamCopier hasta el fin de cada lnea. Desde que se trabaje con este programa en Windows, el carcter de fin de stream es Ctrl-Z. En Unix sera Ctrl-D.

Parte II: Fuentes de datos

Captulo 4. Streams de archivos


Hasta ahora, muchos de los ejemplos en este libro han usado los streams System.in y System.out. Estos son convenientes para ejemplos, pero en la vida real, se har ms comn adjuntar streams a

fuentes de datos como archivos y conexiones a la red. Se usarn las clases java.io.FileInputStream y java.io.FileOutputStream, que son en concreto subclases de java.io.InputStream y java.io.OutputStream, para leer y escribir archivos. FileInputStream y FileOutputStream proveen streams de entrada y salida que te dejan leer y escribir archivos. Se discutirn estas clases a detalle en este captulo; ellas proveen los mtodos estndar para leer y escribir datos. Lo que ellas no proveen es un mecanismo para operaciones especficas de archivos, como averiguar donde un archivo se puede leer o escribir. Para eso, se puede consultar el Captulo 12, que habla acerca de la clase File en s y la forma en que Java trabaja con los archivos.

4.1 Leyendo archivos java.io.FileInputStream es una concreta subclase de java.io.InputStream. Esta provee un stream de entrada conectado a un archivo en particular. public class FileInputStream extends InputStream FileInputStream tiene todos los mtodos usuales de streams de entrada, como read(), available(), skip(), y close(), que son usadas exactamente como cualquier otro stream de entrada. public public public public public public native int read() throws IOException int read(byte[] data) throws IOException int read(byte[] data, int offset, int length) throws IOException native long skip(long n) throws IOException native int available() throws IOException native void close() throws IOException

Estos mtodos son todos implementados en cdigo nativo, excepto para los dos mtodos multibyte read(). Estos, de todas formas, solo pasan sus argumentos a un mtodo nativo privado llamado readBytes(), as que efectivamente todos estos mtodos son implementados en cdigo nativo. (En Java 2, read(byte[] data, int offset, int length) es un mtodo nativo que invoca read(byte[] data).) Hay tres constructores de FileInputStream(), que difieren nicamente en como el archivo ser ledo en especfico: public FileInputStream(String fileName) throws IOException public FileInputStream(File file) throws FileNotFoundException public FileInputStream(FileDescriptor fdObj) El primer constructor usa un string que contiene el nombre del archivo. El segundo constructor usa un objeto java.io.File . El tercer constructor

usa un objeto java.io.FileDescriptor . Los nombres de archivos son independientes de la plataforma, as que los nombres difciles deben ser evitados en lo posible. Usando el primer constructor se violan inmediatamente las reglas de Sun para "100% Java puro". De todas formas, los otros dos constructores son preferidos mucho ms. No obstante, los otros dos tendrn que esperar hasta que los objetos File y descriptor de archivo son discutidos en el Captulo 12. Por ahora, se usar slo el primero. Para leer un archivo, solo pasa el nombre del archivo en el constructor FileInputStream() . Entonces se usa normalmente el mtodo read() . Por ejemplo, el siguiente fragmento de cdigo lee el archivo README.TXT, entonces lo imprime en System.out: try { FileInputStream fis = new FileInputStream("README.TXT"); int n; while ((n = fis.available()) > 0) { byte[] b = new byte[n]; int result = fis.read(b); if (result == -1) break; String s = new String(b); System.out.print(s); } // End while } // End try catch (IOException e) {System.err.println(e);} System.out.println(); Java busca archivos en el current working directory. Generalmente, este es el directorio donde ests cuando escribes java program_name para empezar a correr el programa. Se puede abrir un archivo en un directorio diferente pasando una direccin completa o relativa al archivo desde el actual directorio de trabajo. Por ejemplo, para leer el archivo /etc/hosts sin importar cual directorio es el actual, se puede hacer esto: FileInputStream fis = new FileInputStream("/etc/hosts"); Ntese que este cdigo depende del estilo de Unix en las direcciones. No est garantizado que funcione en Windows o Mac, pero quiz lo haga; algunos entornos de ejecucin como Apple Macintosh Runtime para Java incluye cdigo extra para trasladar del estilo de los nombres de archivo de Unix al estilo nativo. Si el archivo que se est tratando de leer no existe cuando el objeto FileInputStream es construido, una FileNotFoundException (una subclase de java.io.IOException) es lanzada. Si por alguna razn un archivo no puede ser ledo, por ejemplo, el actual proceso no tiene

permiso para leer el archivo, algn otro tipo de IOException es lanzada. El Ejemplo 4.1 lee nombres de archivos desde la lnea de comandos, entonces copia los archivos nombrados a System.out. El mtodo StreamCopier.copy() del Ejemplo 3.3 en el ltimo captulo hace la lectura y escritura actual. Ntese que este mtodo no toma cuidado de si la entrada viene de un archivo o va a la consola. Trabaja sin tener en cuenta del tipo de la entrada y salida que el stream este copiando. Trabajara igualmente bien para otros streams que faltan por introducir, incluyendo los que ni siquiera existan cuando StreamCopier fue creado.

Ejemplo 4.1. El programa FileTyper import java.io.*; import com.macfaq.io.*; public class FileTyper { public static void main(String[] args) { if (args.length == 0) { System.err.println("Usage: java FileTyper file1 file2 ..."); return; } for (int i = 0; i < args.length; i++) { try { typeFile(args[i]); if (i+1 < args.length) { // more files to type System.out.println(); System.out.println("------------------------------------"); } } catch (IOException e) {System.err.println(e);} } } public static void typeFile(String filename) throws IOException { FileInputStream fin = new FileInputStream(filename); StreamCopier.copy(fin, System.out); fin.close(); } }

Los applets que no son de confianza usualmente no permiten leer o escribir archivos. Si tu applet trata de crear un FileInputStream, el constructor va a lanzar una SecurityException.

La clase FileInputStream tiene un mtodo que no est declarado en la superclase InputStream, getFD().

public final FileDescriptor getFD() throws IOException

Este mtodo regresa el objeto java.io.FileDescriptor asociado con este stream. Los objetos descriptores de archivos son discutidos en el Captulo 12. Por ahora, todo lo que tienes que hacer con este objeto es usarlo para crear otro stream de archivo. FileInputStream adems tiene un mtodo protegido finalize() que es invocado cuando un objeto FileInputStream se 'colecta' a la 'basura'. Este mtodo asegura que los archivos son propiamente cerrados antes de que el stream de entrada de archivo que se abri sea recogido a la basura:

protected void finalize() throws IOException

Normalmente no se necesita invocar este mtodo explcitamente, pero si t haces una subclase FileInputStream (algo que nunca he encontrado necesario), se debe invocar a super.finalize() desde el mtodo finalize() de la subclase. Es posible abrir mltiples streams de entrada para el mismo archivo al mismo tiempo, aunque es raramente necesario hacerlo. Cada stream mantiene un puntero separado para la posicin actual en el archivo. Leer desde el archivo no cambia el archivo en ninguna forma. Escribir a un archivo es una historia diferente, esto se ver en la siguiente seccin.

4.2 Escribiendo archivos La clase java.io.FileOutoutStream es una subclase en concreto de java.io.OutputStream que provee streams de salida conectados a los archivos.

public class FileOutputStream extends OutputStream

Esta clase tiene todos los mtodos usuales de los streams de entrada como es write(), flush(), y close(), que son usados como ellos son por cualquier otro stream de salida.

public public public public

native void write(int b) throws IOException void write(byte[] data) throws IOException void write(byte[] data, int offset, int length) throws IOException native void close() throws IOException

Estos son todos los cdigos nativos implementados excepto por los dos mtodos write() multibyte. Estos de todas formas, solo pasan sus argumentos en un mtodo privado nativo llamado writeBytes(), as que efectivamente todos estos mtodos son implementados con cdigo nativo. Hay tres constructores principales de FileOutputStream(), difieren principalmente en como el archivo es especificado:

public FileOutputStream(String filename) throws IOException public FileOutputStream(File file) throws IOException public FileOutputStream(FileDescriptor fd)

El primer constructor usa un string que contiene el nombre del archivo; el segundo constructor usa un objeto java.io.File; el tercer constructor usa un objeto java.io.FileDescriptor. Se evitar usar los constructores segundo y tercero hasta que se hayan discutido los objetos File y FileDescriptor (Captulo 12). Para escribir datos a un archivo, solo hay que pasar el nombre del archivo al constructor de FileOutputStream(), entonces usa los mtodos write() normalmente. Si el archivo no existe, cualquiera de los tres constructores lo crear. Si el archivo existe, cualquier dato dentro de l ser reemplazado. Un cuarto constructor adems permite especificar donde debe borrarse el contenido del archivo antes de que los datos sean escritos en l (append == false) o donde los datos son para ser tomados al fin del archivo (append == true). Los otros tres constructores simplemente sobrescriben el archivo; ellos no proveen una opcin para anexar ms datos al archivo.

public FileOutputStream(String IOException

name,

boolean

append)

throws

Java busca por los archivos en el directorio (carpeta) actual de trabajo. Se puede escribir a un archivo en un directorio diferente pasando una direccin completa o relativa al archivo desde el directorio actual de trabajo. Por ejemplo, para anexar datos al archivo \Windows\java\javalog.txt no importa cual directorio es el actual, se podra hacer esto:

FileOutputStream fout = new FileOutputStream("/Windows/java/javalog.txt", true); Aunque Windows usa una barra invertida como separador de directorios, Java sigue esperando que uses una barra diagonal como en Unix, al menos en Java 1.1. Las rutas codificadas son peligrosamente dependientes de la plataforma. Usando este constructor automticamente clasifica tu programa como Java impuro. Se tomar esto con ms detalle en el Captulo 12. Los applets que no son de confianza son normalmente no tienen permitido leer o escribir archivos. Si un applet trata de crear un FileOutputStream, el constructor lanza una SecurityException. La clase FileOutputStream tiene un mtodo que no est declarado en java.io.OutputStream, getFD():

public final FileDescriptor getFD() throws IOException

Este mtodo regresa el objeto java.io.FileDescriptor asociado con este stream. La clase FileOutputStream adems tiene un mtodo protegido finalize() que es invocado antes de que un objeto FileOutputStream sea "colectado a la basura". Este mtodo asegura que los archivos son propiamente limpiados y cerrados antes de que el stream de salida que lo abri sea colectado. Normalmente no se necesita invocar este mtodo explcitamente. Si se hace una subclase de FileOutputSteam y se reemplaza a finalize(), el mtodo de la subclase finalize() debe invocar este mtodo finalize llamando a super.finalize().

El Ejemplo 4.2 lee dos nombres de archivos desde la lnea de comandos, entonces copia el primer archivo en el segundo archivo, La clase StreamCopier del Ejemplo 3.3 en el captulo anterior es usada para hacer la escritura y lectura actual.

Ejemplo 4.2 El programa FileCopier import java.io.*; import com.macfaq.io.*; public class FileCopier { public static void main(String[] args) { if (args.length != 2) { System.err.println("Usage: java FileCopier infile outfile"); } try { copy(args[0], args[1]); } catch (IOException e) {System.err.println(e);} } public static void copy(String inFile, String outFile) throws IOException { FileInputStream fin = null; FileOutputStream fout = null; try { fin = new FileInputStream(inFile); fout = new FileOutputStream(outFile); StreamCopier.copy(fin, fout); } finally { try { if (fin != null) fin.close(); } catch (IOException e) { } try { if (fout != null) fout.close(); } catch (IOException e) { } } } }

Desde que no se escribe ms a System.out y no se lee ms desde

System.in, es importante asegurarse de que los streams son cerrados cuando se haya acabado. Este es un buen uso para una clausula finally, para nuestra necesidad de asegurarse de que los archivos se cierren tengan xito o no las lecturas y escrituras. Java es mejor cerrando archivos que muchos lenguajes. Mientras la VM (Mquina Virtual) no termine anormalmente, los archivos sern cerrados cuando el programa salga. A pesar de todo, si esta clase es usada dentro de un programa de larga duracin como un servidor web, esperar hasta que el programa salga no es una buena idea; otro threads y procesos quiz necesiten acceso a los archivos. Hay un error en este programa: no se comporta bien si los archivos de entrada y salida son el mismo. Mientras sea sencillo comparar los dos nombres de archivos antes de copiarlos, esto no es suficientemente seguro. Existen alias, atajos, enlaces simblicos, y otros factores que son tomados en cuenta, un solo archivo quiz tengan mltiples nombres. La solucin completa a este problema tendr que esperar hasta el Captulo 12, cuando se discuta los caminos cannicos y los archivos temporales.

4.3 Visor de archivos, parte 1 Muchas veces se encuentra til abrir un archivo arbitrario e interpretarlo en una forma arbitraria. Ms comnmente, se desea ver un archivo como texto, pero ocasionalmente, es til interpretarlo como enteros hexadecimales, como datos IEEE 745 de punto flotante, o algn otro. Se va a desarrollar un programa que te deja abrir cualquier archivo y ver su contenido en una variedad de formas diferentes. En cada captulo, se agregar una pieza al programa hasta que sea completamente funcional. Ahora este es solo el inicio del programa, es importante mantener el cdigo tan general y adaptable como sea posible. El Ejemplo 4.3 lee una serie de nombres de archivos desde la lnea de comandos en el mtodo main(). Cada nombre de archivo es pasado a un mtodo que abre el archivo. Los datos del archivo son ledos e impresos en System.out. Exactamente como el dato es impreso en System.out es determinado por un switch de lnea de comandos. Si el usuario selecciona el formato ASCII (-a), entonces se asumir que los datos sern texto ASCII (ms propiamente, ISO Latin-1) e impresos como chars. Si el usuario selecciona volcado de decimales (-d), entonces cada byte debe ser impreso como un numero decimal sin firmar entre 255, 16 por lnea. Por ejemplo: 000 234 127 034 234 234 000 000 000 002 004 070 000 234 127 098

Los ceros son usados para mantener un tamao constante para los valores de bytes y para cada lnea. Un simple algoritmo de seleccin es usado para determinar cuntos ceros se deben adjuntar a cada nmero. Para el volcado de formato hex (-h), cada byte debe ser impreso como dos dgitos hexadecimales. Por ejemplo: CA FE BA BE 07 89 9A 65 45 65 43 6F F6 7F 8F EE E5 67 63 26 98 9E 9C La codificacin hexadecimal es la ms fcil, porque cada byte es siempre exactamente dos dgitos hex. El mtodo esttico Integer.toHexString() es usado para convertir cada lectura de byte en dos dgitos hexadecimales. El formato ASCII es el 'default' y es el ms simple de implementar. Esta conversin puede ser lograda simplemente copiando los datos de entrada a la consola. Ejemplo 4.3 El programa FileDumper import java.io.*; import com.macfaq.io.*; public class FileDumper { public static final int ASC = 0; public static final int DEC = 1; public static final int HEX = 2; public static void main(String[] args) { if (args.length < 1) { System.err.println("Usage: java FileDumper [-ahd] file1 file2..."); return; } int firstArg = 0; int mode = ASC; if (args[0].startsWith("-")) { firstArg = 1; if (args[0].equals("-h")) mode = HEX; else if (args[0].equals("-d")) mode = DEC; } for (int i = firstArg; i < args.length; i++) { if (mode == ASC) dumpAscii(args[i]); else if (mode == HEX) dumpHex(args[i]); else if (mode == DEC) dumpDecimal(args[i]); if (i < args.length-1) { // more files to dump System.out.println("\r\n-------------------------------------\r\n"); } }

} public static void dumpAscii(String filename) { FileInputStream fin = null; try { fin = new FileInputStream(filename); StreamCopier.copy(fin, System.out); } catch (IOException e) {System.err.println(e);} finally { try { if (fin != null) fin.close(); } catch (IOException e) { } } } public static void dumpDecimal(String filename) { FileInputStream fin = null; byte[] buffer = new byte[16]; boolean end = false; int bytesRead; try { fin = new FileInputStream(filename); while (!end) { bytesRead = 0; while (bytesRead < buffer.length) { int r = fin.read(buffer, bytesRead, buffer.length - bytesRead); if (r == -1) { end = true; break; } bytesRead += r; } for (int i = 0; i < bytesRead; i++) { int dec = buffer[i]; if (dec < 0) dec = 256 + dec; if (dec < 10) System.out.print("00" + dec + " "); else if (dec < 100) System.out.print("0" + dec + " "); else System.out.print(dec + " "); } System.out.println(); } } catch (IOException e) {System.err.println(e);} finally { try { if (fin != null) fin.close();

} catch (IOException e) { } } } public static void dumpHex(String filename) { FileInputStream fin = null; byte[] buffer = new byte[24]; boolean end = false; int bytesRead; try { fin = new FileInputStream(filename); while (!end) { bytesRead = 0; while (bytesRead < buffer.length) { int r = fin.read(buffer, bytesRead, buffer.length - bytesRead); if (r == -1) { end = true; break; } bytesRead += r; } for (int i = 0; i < bytesRead; i++) { int hex = buffer[i]; if (hex < 0) hex = 256 + hex; if (hex >= 16) System.out.print(Integer.toHexString(hex) + " "); else System.out.print("0" + Integer.toHexString(hex) + " "); } System.out.println(); } } catch (IOException e) {System.err.println(e);} finally { try { if (fin != null) fin.close(); } catch (IOException e) { } } } }

Cuando FileDumper es usado para descargarse su propio archivo .class en formato hexadecimal, produce lo siguiente: D:\JAVA\ioexamples\04>java FileDumper -h FileDumper.class ca fe ba be 00 03 00 2d 00 7e 03 00 00 00 00 03 00 00 00 01 03 00 00

00 02 08 00 43 08 00 44 08 00 52 08 00 53 08 00 54 08 00 55 08 00 56 08 00 63 07 00 5c 07 00 66 07 00 6d 07 00 6e 07 00 6f 07 00 70 07 00 71 07 00 En siguientes captulos, se agregar una GUI (Interfaz Grfica de Usuario) y muchas ms posibles interpretaciones de los datos en el archivo, incluyendo punto flotante (float), entero big- y little-endian, y varias codificaciones de texto.

Parte IV: Temas avanzados y otros

Captulo 12. Trabajando con archivos


Ya se ha aprendido como leer y escribir datos en archivos usando streams de entrada de archivos y streams de salida de archivos. Eso no es todo lo que hay en los archivos. Los archivos pueden ser creados, movidos, renombrados, copiados, borrados y manipulados de otras formas sin respeto a sus contenidos. Los archivos son adems muchas veces asociados con meta-informacin que no es estrictamente parte de los contenidos del archivo, como la hora en que el archivo fue creado, el icono para el archivo, los permisos que determinan que usuarios pueden leer o escribir en el archivo, e incluso el nombre del archivo. Mientras la abstraccin de los contenidos de un archivo como una secuencia de bytes ordenados usados por el flujo de entrada y salida de archivos es casi un estndar sobre las plataformas (sistema operativo), la meta-informacin no lo es. La clase java.io.File intenta proveer una abstraccin independiente de la plataforma para las operaciones comunes de los archivos y meta-informacin. Desafortunadamente, esta clase realmente muestra sus races Unix. Trabaja bien en Unix, adecuadamente en Windows y OS/2 (con algunas

advertencias) y falla miserablemente en Macintosh. Java 2 mejora las cosas, pero todava hay mucha historia y llegar a algo que genuinamente trabaje en todas las plataformas es un problema extremadamente difcil. La manipulacin de archivos es por lo tanto una de las dificultades reales de la programacin en Java sobre las plataformas. Antes de esperar escribir cdigo real independiente de plataforma, se necesita un slido entendimiento de los bsicos sistemas de archivos en todas las plataformas. Este captulo trata de cubrir aquellos bsicos para la mayora de las plataformas que Java soporta: Unix; DOS/Windows 3x; Windows 95, 98, y NT; OS/2; y Mac, entonces te muestran cmo escribir el cdigo del archivo de manera que sea tan portable como sea posible.

12.1 Entendiendo los archivos

Das könnte Ihnen auch gefallen