P. 1
Manejo de Archivos en Java - iO Leer - Escribir Programacion

Manejo de Archivos en Java - iO Leer - Escribir Programacion

|Views: 11.075|Likes:
Publicado porkike01may

More info:

Published by: kike01may on Feb 13, 2010
Copyright:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as DOC, PDF, TXT or read online from Scribd
See more
See less

08/06/2013

pdf

text

original

Tutoriales I/O: Leer y Escibir Autor: Sun Traductor: Juan Antonio Palos (Ozito

)

• • • • • •

• •

• •

• •

I/O: Leer y Escribir o Introducción a los Streams I/O o Serialización de Objetos o Trabajar con Ficheros de Acceso Aleatorio o Y el Resto... Introducción a los Streams de I/O o Streams de Caracteres o Streams de Bytes o Entender las Superclases de I/O Usar Strings de Profundidad Como usar Streams de Ficheros Como usar Piped Streams o Usar Streams para Envolver otros Streams Usar Streams de Proceso Cómo Concatenar Ficheros Trabajar con Streams Filtrados o Usar Streams Filtrados o Cómo usar DataInputStream y DataOutputStream o Escribir Nuestros Propios Streams Filtrados Cómo usar DataInputStream y DataOutputStream Escribir Streams Filtrados o La clase CheckedOutputStream o La Clase CheckedInputStream o El Interface Checksum y la clase Adler32 o Un Programa de Prueba o Filtrar Ficheros de Acceso Aleatorio Serialización de Objetos Serializar Objetos o ¿Cómo Escribir en un ObjectOutputStream? o ¿Cómo Leer desde un ObjectInputStream? Proporcionar Serialización de Objetos para Nuestras Clases o Implementar el Interface Serializable o Personalizar la Serialización o Implementar el Interface Externalizable o Proteger la Información Sensible Trabajar con Ficheros de Acceso Aleatorio o Usar Ficheros de Acceso Aleatorio o Escribir Filtros para Ficheros de Acceso Aleatorio Usar Ficheros de Acceso Aleatorio Escribir Filtros para Ficheros de Acceso Aleatorio o CheckedDataOutput contra CheckedOutputStream o CheckedDataInput contra CheckedInputStream o Los Programas Principales Y el Resto...

1

I/O: Leer y Escribir Frecuentemente los programas necesitan traer información desde una fuente externa o enviar información a una fuente externa. La información pueder estár en cualquier parte, en un fichero, en disco, en algún lugar de la red, en memoria o en otro programa. También puede ser de cualquier tipo: objetos, caracteres, imágenes o sonidos. Para traer la información, un programa abre un stream sobre una fuente de información (un fichero, memoria, un socket) y lee la información serialmente, de esta forma:

Similarmente, un programa puede enviar información a un destino externo abriendo un stream sobre un destino y escribiendo la información serialmente, de esta forma:

No importa de donde venga o donde vaya la información y tampoco importa el tipo de los datos que están siendo leídos o escritos, los algoritmos para leer y escribir son casi siempre los mismos. Leer abrir un stream mientras haya información leer información cerrar el stream Escribir abrir un stream mientras haya información escribir información cerrar el stream

El paquete java.io contiene una colección de clases stream que soportan estos algoritmos para leer y escribir. Estas clases están divididas en dos árboles basándose en los tipos de datos (caracteres o bytes) sobre los que opera.

2

Sin embargo, algunas veces es más conveniente agrupar las clases basándose en su propósito en vez en los tipos de datos que lee o escribe. Así, podemos agrupar los streams dependiendo de si leen u escriben lados en las "profundidades" o procesan la información que está siendo leída o escrita.

Introducción a los Streams I/O Esta sección describe todos los tipos de streams y muestra las clases del paquete java.io que los implementan de acuerdo a la división del árbol de clases. Luego, como mucha gente piensa en términos de lo que quieren hacer en vez de lo que están haciendo, proporcionamos dos secciones que nos muestran cómo usar los streams seleccionados basándonos en su propósito.

Introdución a los Streams de I/O o Streams de Caracteres o Streams de Bytes o Entender las Superclases de I/O

Introdución a los Streams de I/O Streams de Caracteres Reader y Writer son las superclases abstractas para streams de caracteres en java.io.

3

Reader proporciona el API y una implementación para readers-- streams que leen caracteres de 16-bits-- y Writer proporciona el API y una implementación para writers-streams que escriben caracteres de 16-bits. Las subclases de Reader y Writer que implementan streams especializados se dividen en dos categorías: aquellos que leen o escriben datos en profundidad (sinks) [mostrados en gris en la siguiente figura] y aquellos que realizan alguna suerte de proceso [mostrados en blanco]. La figura muestra el árbol de clases para Reader y Writer.

La mayoría de los programas deberían usar este tipo de streams, ya que ambos pueden manejar caracteres en el conjunto de caracteres Unicode (mientras que los streams de bytes están limitados a bytes ISO-Latin-1 de 8-bit). Streams de Bytes Los programas deberían usar los streams de bytes, descendientes de InputStream y OutputStream, para leer y escribir bytes de 8-bits. InputStream y OutputStream proporcionan el API y alguna implementación streams de entrada y salida y leen y escriben bytes de 8-bits. Estos streams se usan normalmente para leer y escribir datos binarios como imágenes y sonidos. Al igual que Reader y Writer, las subclases de InputStream y OutputStream proporcionan I/O especializada que se divide en dos categorias: streams de profundidad y streams de procesamiento. La siguiente figura muestra el árbol de clases para los streams de bytes.

4

Como se ha mencionado, dos de las clases de streams de bytes, ObjectInputStream y ObjectOutputStream, se usan para la serialización de objetos. Esta clases se cubren completamente en la página Serialización de Objetos. Entender las Superclases de I/O Reader y InputStream definen APIs similares pero para diferentes tipos de datos. Por ejemplo, Reader contiene estos métodos para leer caracteres y arrays de caracteres: int read() int read(char cbuf[]) int read(char cbuf[], int offset, int length) InputStream defien los mismos métodos pero para leer bytes o arrays de bytes: int read() int read(byte cbuf[]) int read(byte cbuf[], int offset, int length) Ambos también proporcionan métodos para marcar localizaciones en el stream, saltar entradas y restablecer la posición actual. Writer y OutputStream son similarmente paralelas. Writer define tres métodos para escribir caracteres y arrays de caracteres. int write(int c) int write(char cbuf[]) int write(char cbuf[], int offset, int length) Y OutputStream define los mismos métodos pero para Bytes.

5

int write(int c) int write(byte cbuf[]) int write(byte cbuf[], int offset, int length) Todos estos streams --readers, writers, inputstreams, y outputstreams-- se abren automáticamente cuando se crean. Podemos cerrar explícitamente cualquier stream llamando a su método close. O el recolector de basura puede cerrarlos implícitmamente, lo que ocurre cuando se deja de referenciar el objeto.

Usar Strings de Profundidad

Usar Strings de Profundidad Los streams de profundidad "sink" leen o escriben datos desde sitios especializados como strings, ficheros o tuberías (pipes). Típicamente, cada reader o inputstream está pensado para un tipo específico de fuente de entrada, java.io contiene un writer o un outputstream paralelo que pueden crearlo. La siguiente tabla nos muestra los streams de datos sink de java.io: Tipo de Sink Streams de Caracteres Streams de Bytes Memory CharArrayReader, ByteArrayInputStream, CharArrayWriter, ByteArrayOutputStream, StringReader, StringBufferInputStream StringWriter Pipe PipedReader, PipedInputStream, PipedWriter PipedOutputStream File FileReader, FileInputStream, FileWriter FileOutputStream Observa que tanto el grupo de streams de caracteres como el bytes contienen parejas paralelas que operan con el tipo de sinks de datos. Estos se describen luego. CharArrayReader y CharArrayWriter ByteArrayInputStream y ByteArrayOutputStream Estos streams se usan para leer y escribir desde memoria. Podemos crear estos streams sobre un array existente y luego usara los métodos de lectura y escritura para leer y escribir desde el array. FileReader y FileWriter FileInputStream y FileOutputStream Colectivamente llamados streams de ficheros, estos streams se usan para leer y escribir ficheros del sistema de ficheros nativo. Como usar Streams de Ficheros tiene un ejemplo que usa FileReader y FileWriter para copiar el contenido de un fichror a otro. PipedReader y PipedWriter PipedInputStream y PipedOutputStream Implementan los componentes de entrada y salida de una tubería.

6

Las tuberías (Pipes) se usan para canalizar la salida de un programa (o thread) hacia la entrada de otro programa (o thread). Puedes ver PipedReader y PipedWriter en acción en la página Como usar Pipe Streams. StringReader y StringWriter StringBufferInputStream Se usa StringReader para leer caracteres desde un String que reside en memoria. Se usa StringWriter para escribir en un String. StringWriter recoge los caracteres escritos en un StringBuffer, que puede ser convertido en un String. StringBufferInputStream es similar a StringReader, excepto en que lee bytes desde un StringBuffer.

Como usar Streams de Ficheros

Como usar Streams de Ficheros Los streams de ficheros son quizás los más fáciles de entender. Simplemente ponemos, el stream de ficheros --FileReader, FileWriter, FileInputStream, y FileOutputStream-- cada uno de lectura o escritura sobre un fichero del sistema de ficheros nativo. Podemos crear un stream de fichero desde un nombre de fichero en el formato de un string, desde un objeto File, o desde un objeto FileDescriptor. El siguiente programa Copy usa FileReader y FileWriter para copiar el contenido de un fichero llamado farrago.txt en otro fichero llamado outagain.txt. import java.io.*; public class Copy { public static void main(String[] args) throws IOException { File inputFile = new File("farrago.txt"); File outputFile = new File("outagain.txt"); FileReader in = new FileReader(inputFile); FileWriter out = new FileWriter(outputFile); int c; while ((c = in.read()) != -1) out.write(c); in.close(); out.close(); } } Este programa es muy sencillo.

7

Abre FileReader sobre farrago.txt y abre FileWriter sobre outagain.txt. El programa lee caracteres desde el reader mientras haya más entradas en el fichero de entrada. Cuando la entrada se acada, el programa cierra tanto el reader como el writer. Observa el código que usa el programa Copy para crear un FileReader. File inputFile = new File("farrago.txt"); FileReader in = new FileReader(inputFile); Este código crea un objeto File que representa el fichero nombrado en el sistema de ficheros nativo. File es una clase de utilidad proporcionada por java.io. Este programa usa este objeto sólo para construir un FileReader sobre farrago.txt. Sin embargo, se podría usar inputFile para obtener información sobre farrago.txt, como su path completo. Después de haber ejecutado el programa, deberíamos encontrar una copia exacta de farrago.txt en un fichero llamado outagain.txt en el mismo directorio. Aquí está el contenido del fichero: So she went into the garden to cut a cabbage-leaf, to make an apple-pie; and at the same time a great she-bear, coming up the street, pops its head into the shop. 'What! no soap?' So he died, and she very imprudently married the barber; and there were present the Picninnies, and the Joblillies, and the Garyalies, and the grand Panjandrum himself, with the little round button at top, and they all fell to playing the game of catch as catch can, till the gun powder ran out at the heels of their boots. Samuel Foote 1720-1777 Recuerda que FileReader y FileWriter leen y escriben caracteres de 16 bits. Sin embargo, la mayoría del sistemas de ficheros nativos están basados en bytes de 8 bits. Estos streams codifican los caracteres según operan de acuerdo al esquema de codificación de caracteres por defecto. Podemos encontrar la codificación de caracteres por defecto usando System.getProperty("file.encoding"). Para especificar otra codificación, deberíamos construir un OutputStreamWriter sobre un FileOutputStream y especificarla. Para más información sobre la codificación de caracteres puedes ver la sección Internationalization. Para curiosos, aquí tenemos otra versión de este programa, CopyBytes, que usa FileInputStream y FileOutputStream en lugar de FileReader y FileWriter.

Como usar Piped Streams o Usar Streams para Envolver otros Streams

Como usar Piped Streams

8

PipedReader y PipedWriter (y sus correspondientes streams de entrada y salida PipedInputStream y PipedOutputStream) implementan los componentes de entrada y salida de una tubería. Las tuberías se utilizan para canalizar la salida de un programa (o thread) a la entrada de otro programa (o thread) ¿Por qué son útiles? Consideremos una clase que implementa varias utilidades de manipulación de strings como ordenación o inversión de texto. Sería bonito que la salida de uno de estos métodos puediera ser usada como entrada del otro para que pudieramos encadenar una serie de llamadas a métodos que realizan un función de gran importancia. Por ejemplo, podríamos invertir todas las palabras de una lista, ordenarlas, y volver a invertirlas para crear una lista de palabrás rítmicas. Sin los streams de tuberías, el programa debería almacenar los resultados en algún lugar (como en un fichero o en la memoria) entre cada paso, como se ve aquí:

Con los streams de tuberías, la salida de un método puede ser dirigida hacia la entrada del siguiente, como se muestra en esta figura:

Luego investigaremos un programa que implementa lo que representa el diagrama de la figura anterior. Este programa usa PipedReader y PipedWriter para conectar la entrada y la salida de sus métodos reverse y sort para crear una lista de palabras rítmicas. Este programa se compone de varias clases. Esta sección muestra y explica sólo los elementos del programa que leen y escriben en las tuberías. Sigue los siguientes enlaces al código para ver el programa completo. Primero, echemos un vistazo a la secuencia de llamada de los métodos reverse y sort desde el método main en la clase RhymingWords. FileReader words = new FileReader("words.txt"); Reader rhymingWords = reverse(sort(reverse(words)));

9

La llamada más interna a reverse toma un FileReader abierto sobre el fichero words.txt que contiene una lista de palabras. El valor devuelto por reverse se pasa a sort, cuyo valor de retorno es pasado a otra llamada a reverse. Echemos un vistazo al método reverse; el método sort es similiar y lo entenderemos una vez que comprendamos reverse. public static Reader reverse(Reader source) { BufferedReader in = new BufferedReader(source); PipedWriter pipeOut = new PipedWriter(); PipedReader pipeIn = new PipedReader(pipeOut); PrintWriter out = new PrintWriter(pipeOut); new ReverseThread(out, in).start(); return pipeIn; } Las sentencias en negrita de reverse crean los dos puntos finales de una tubería --un PipedWriter y un PipedReader-- y los conecta construyendo el PipedReader "sobre" el PipedWriter. Cualquier cosa escrita en el PipedWriter puede ser leída desde el PipedReader. Las formas de conexión de tuberías se ilustran aquí:.

reverse arranca un ReverseThread que escribe su salida en el PipedWriter y devuelve el PipedReader al llamador. Entonces el llamador preprara un thread de ordenación para leerla. El método sort es exactamente lo mismo, excepto en que crea y arranca un SortThread. Usar Streams para Envolver otros Streams El método reverse contiene algún código interesante; en particular estas dos sentencias: BufferedReader in = new BufferedReader(source); ... PrintWriter out = new PrintWriter(pipeOut); La primera línea abre un BufferedReader sobre source, el argumento a invertir (un Reader). Esto esencialmente "envuelve" source en un BufferedReader.

10

El programa lee desde el BufferedReader, que a su vez lee desde source. El programa hace esto para poder usar el método de conveniencia readLine de BufferedReader. De forma similar, el PipedWriter es envuelto en un PrintWriter para que el programa pueda usar el método de conveniencia println de PrintWriter. Frecuentemente veremos estreams envueltos de esta forma para así combinar las distintas características de varios streams. Intenta esto: Escribe otra versión de este programa que use inputstreams y outputstreams en vez de readers y writers. Aquí puedes ver las soluciones:
• • •

RhymingWords ReverseThread SortThread

Usar Streams de Proceso

Usar Streams de Proceso Los streams de proceso realizan alguna suerte de operación, como almacenamiento o codificación de caracteres, mientras leen o escriben. Al igual que de los stream de profundidad, java.io contiene parejas de streams. Uno que realiza una operación particular durante la lectura y otro que realiza la misma operación (o la inversa) durante la escritura. Esta tabla nos muestra los streams de proceso de java.io: Proceso Almacenamiento Stream de Caracteres Streams de Bytes BufferedReader, BufferedInputStream, BufferedWriter BufferedOutputStream Filtrado FilterReader, FilterInputStream, FilterWriter FilterOutputStream Conversión entre InputStreamReader, Bytes y Caracteres OutputStreamWriter Concatenación SequenceInputStream Serialización de Objetos ObjectInputStream, ObjectOutputStream Conversión de Datos DataInputStream, DataOutputStream Contaje LineNumberReader LineNumberInputStream Exploración PushbackReader PushbackInputStream Impresión PrintWriter PrintStream

Observa que muchas veces, java.io contiene streams de caracteres y de bytes que realizan el mismo proceso pero para diferentes tipos de datos.

11

BufferedReader y BufferedWriter BufferedInputStream y BufferedOutputStream Almacenan los datos en buffers mientras leen o escriben, por lo tanto reduciendo así el número de accesos requeridos a la fuente de datos original. Los streams con buffer normalmente son más eficientes que los que no lo utilizan. FilterReader y FilterWriter FilterInputStream y FilterOutputStream Clases abstractas, como sus padres. Definen el interface para filtros de streams, que filtran los datos que están siendo leídos o escritos. Trabajar con Streams Filtrados más adelante en esta lección, nos mostrará como usar filtros de streams y como implementar el nuestro propio. InputStreamReader y OutputStreamWriter Una pareja de reader y writer que realiza un puente entre streams de bytes y streams de caracteres. Un InputStreamReader lee bytes desde un InputStream y los convierte a caracteres usando la decodificación de caracteres por defecto o una decodificación de caracteres especificada por su nombre. De forma similar, un OutputStreamWriter convierte caracteres a bytes usando una decodificación de caracteres por defecto o una decodificación de caracteres especificada por su nombre y luego escribe estos bytes en un OutputStream. Podemos aprender el nombre de la codificación de caracteres por defecto llamando a System.getProperty("file.encoding"). SequenceInputStream Concatena varios streams de entrada en un sólo stream de entrada. Cómo Contanerar Ficheros tiene un pequeño ejemplo de esta clase. ObjectInputStream y ObjectOutputStream Se usa para serializar objetos. Puedes ver Serialización de Objetos. DataInputStream y DataOutputStream Lee o escribir tipos de datos primitivos de Java de una forma independiente de la máquina. Cómo usar DataInputStream y DataOutputStream nos enseña un ejemplo de uso de estos dos streams. LineNumberReader y LineNumberInputStream Sigue la pista del número de línea mientras lee. PushbackReader y PushbackInputStream Dos streams cada uno con un caracter (o byte) de menos en el buffer. Algunas veces, cuando se leen datos desde un stream, encontraremos útil explorar el siguiente ítem del stream para decidir que hacer luego. Sin embargo, si lo exploramos, necesitaremos ponerlo de nuevo en su sitio para que pueda ser leído y procesado normalmente. PrintWriter y PrintStream Contienen métodos de impresión de conveniencia. Estos son streams sencillos para escribir, por eso frecuentemente veremos otros streams envueltos en uno de estos.

12

Cómo Concatenar Ficheros

Cómo Concatenar Ficheros SequenceInputStream crea un sólo stream de entrada desde varias fuentes de entrada. Este programa de ejemplo Concatenate, usa SequenceInputStream para implementar la utilidad de concatenación que secuencialmente concatena ficheros en el orden que son listados en la línea de comandos: Esta es la clase controladora de la utilidad Concatenate: import java.io.*; public class Concatenate { public static void main(String[] args) throws IOException { ListOfFiles mylist = new ListOfFiles(args); SequenceInputStream s = new SequenceInputStream(mylist); int c; while ((c = s.read()) != -1) System.out.write(c); s.close(); } } Lo primero que hace esta clase es crear un objeto ListOfFiles llamado mylist que es inicializado con los argumentos de la línea de comandos introducidos por el usuario. Los argumentos de la línea de comandos listan los ficheros a concatenar. Se usa mylist para inicializar SequenceInputStream que usa mylist para obtener un nuevo InputStream para cada ficheros de lista: import java.util.*; import java.io.*; public class ListOfFiles implements Enumeration { private String[] listOfFiles; private int current = 0; public ListOfFiles(String[] listOfFiles) { this.listOfFiles = listOfFiles; } public boolean hasMoreElements() { if (current < listOfFiles.length) return true; else return false; }

13

public Object nextElement() { InputStream in = null; if (!hasMoreElements()) throw new NoSuchElementException("No more files."); else { String nextElement = listOfFiles[current]; current++; try { in = new FileInputStream(nextElement); } catch (FileNotFoundException e) { System.err.println("ListOfFiles: Can't open " + nextElement); } } return in; } } ListOfFiles implementa el interface Enumeration. Veremos como esto entra en juego cuando pasemos por el resto del programa. Después el método main crea el SequenceInputStream, lee un byte cada vez. Cuando el SequenceInputStream necesita un InputStream de una nueva fuente (como para el primer byte leído o cuando alcanza el final del inputstream actual), llama a nextElement sobre el objeto Enumeration para obetener el siguiente InputStream. ListOfFiles crea objetos FileInputStream enforma de lazo, lo que significa que siempre que SequenceInputStream llama a nextElement, ListOfFiles abre un FileInputStream sobre el siguiente nombre de ficheros de la lista y devuelve el stream. Cuando el ListOfFiles llega al final de los ficheros a leer (no tiene más elementos), nextElement devuelve null, y la llamada al método read de SequenceInputStream devuelve -1 para indicar el final de la entrada. Concatenate simplemente hace eco de SequenceInputStream a la salida estándar. Prueba Esto: Intenta ejecutar Concatenate sobre los ficheros farrago.txt y words.txt que han sido usados como entradas para otros ejemplos de esta lección. todos los datos leidos desde el

Trabajar con Streams Filtrados o Usar Streams Filtrados o Cómo usar DataInputStream y DataOutputStream o Escribir Nuestros Propios Streams Filtrados

Trabajar con Streams Filtrados

14

Añadimos un stream filtrado a otro stream para filtrar los datos que están siendo leídos o escritos desde el stream original. El paquete java.io contiene estos FilterInputStream o FilterOutputStream.
• • • • •

streams

filtrados

que

son

subclases

de

DataInputStream y DataOutputStream BufferedInputStream y BufferedOutputStream LineNumberInputStream PushbackInputStream PrintStream (este es un estream de salida)

Esta sección muestra cómo usar streams filtrados a través de un ejemplo que usa un DataInputStream y un DataOutputStream. Además, esta sección muestra como escribir nuestros propios streams filtrados. Usar Streams Filtrados Para usar un stream de entrada o salida filtrado, adjuntamos el stream filtrado a otro stream de entrada o salida. Por ejemplo, podemos adjuntar un DataInputStream al stream de entrada estándar con el siguiente código: DataInputStream dis = new DataInputStream(System.in); String input; while ((input = dis.readLine()) != null) { . . . // do something interesting here } Podríamos hacer esto para poder usar los métodos readXXX más convenientes, como un readLine, implementado por DataInputStream. Cómo usar DataInputStream y DataOutputStream Esta página cubre y explica un ejemplo de uso de DataInputStream y DataOutputStream, dos streams filtrados que pueden leer y escribir tipos de datos primitivos de Java. Escribir Nuestros Propios Streams Filtrados Muchos programadores podrían encontrar que necesitan implementar sus propios streams que filtren o procesen datos que están siendo leídos o escritos desde un stream. Algunas veces el proceso es independiente del formato de los datos, como el contaje de varios ítems de un stream, y algunas veces el proceso está relacionado directamente con los propios datos o su formato, como la lectura y escritura de datos que están contenidos en filas y columnas. Frecuentemente, estos programadores, subclasifican FilterOutputStream y FilterInputStream para conseguir sus objetivos. Esta sección describe un ejemplo de cómo subclasificar FileInputStream y FilterOutputStream para crear nuestos propios filtros.

Cómo usar DataInputStream y DataOutputStream

15

Cómo usar DataInputStream y DataOutputStream Este página muestra cómo usar las clases DataInputStreamy DataOutputStream de java.io. Crea un ejemplo: DataIOTest, que lee y escribe datos tabulares (una factura de merchandising Java). Los datos tabulares están formateados en columnas, donde cada columna está separada de la siguiente por un tab. Las columnas contienen los precios de venta, el número de unidades pedidas, y una descripción del ítem, de esta forma: 19.99 12 9.99 8 Java T-shirt Java Mug

DataOutputStream, al igual que otros streams de salida filtrados, debe adjuntarse a algún otro OutputStream. En este caso, se adjunta a un FileOutputStream que está configurado para escribir en un fichero llamado invoice1.txt. DataOutputStream dos = new DataOutputStream( new FileOutputStream("invoice1.txt")); Luego, DataIOTest usa métodos especializados writeXXXde DataOutputStream para escribir los datos de la factura (contenida dentro de arrays en el programa) de acuerdo a los tipos de datos que se están escribiendo. for (int i = 0; i < prices.length; i ++) { dos.writeDouble(prices[i]); dos.writeChar('\t'); dos.writeInt(units[i]); dos.writeChar('\t'); dos.writeChars(descs[i]); dos.writeChar('\n'); } dos.close(); Observa que este código cierra el stream de salida cuando ha finalizado. Luego, DataIOTest abre un DataInputStream sobre el fichero que acaba de escribir. DataInputStream dis = new DataInputStream( new FileInputStream("invoice1.txt")); DataInputStream también debe adjuntar algún otro InputStream; en este caso, un FileInputStream configurado para leer el fichero que acaba de escribir --invoice1.txt. DataIOTest luego sólo lee los datos usando los métodos especializados readXXX de DataInputStream: try { while (true) { price = dis.readDouble(); dis.readChar(); // throws out the tab unit = dis.readInt();

16

dis.readChar(); // throws out the tab desc = dis.readLine(); System.out.println("You've ordered " + unit + " units of " + desc + " at $" + price); total = total + unit * price; } } catch (EOFException e) { } System.out.println("For a TOTAL of: $" + total); dis.close(); Cuando se han leído todos los datos, DataIOTest muestra una sentencia sumarizando el pedido y la cantidad debida, y cierra el stream. Observa el bucle que usa DataIOTest para leer los datos desde el DataInputStream. Normalmente, cuando se lee usaremos un bucle como este: while ((input = dis.readLine()) != null) { ... } El método readLine devuelve un valor, null, que indica que se ha alcanzado el fin del fichero. Muchos de los métodos readXXX de DataInputStream no pueden hacer esto porque cualquier valor devuelto para indicar fin-de-fichero podría ser un valor leído legítimamente desde el stream. Por ejemplo, supongamos que queremos usar -1 para indicar el fin-de-fichero. Bien, no podemos usarlo porque -1 es un valor legítimo que puede ser leído desde el stream de entrada usando readDouble, readInt, o uno de los otros métodos de lectura que leen números. Por eso el método readXXXde DataInputStream lanza una EOFException en su lugar. Cuando ocurre EOFException termina el while (true). Cuando ejecutemos el programa DataIOTest deberíamos ver la siguiente salida: You've ordered 12 units of Java T-shirt at $19.99 You've ordered 8 units of Java Mug at $9.99 You've ordered 13 units of Duke Juggling Dolls at $15.99 You've ordered 29 units of Java Pin at $3.99 You've ordered 50 units of Java Key Chain at $4.99 For a TOTAL of: $892.88

Escribir Streams Filtrados o La clase CheckedOutputStream o La Clase CheckedInputStream o El Interface Checksum y la clase Adler32 o Un Programa de Prueba o Filtrar Ficheros de Acceso Aleatorio

17

Escribir Streams Filtrados Lo siguiente es una lista de pasos a realizar cuando escribamos nuestro propios streams filtrados tanto de entrada como de salida.
• • • •

Crear una subclase de FilterInputStream y FilterOutputStream. Normalmente los streams de entrada y salida vienen en parejas, por eso necesitamos crear las dos versión del stream filtrado. Sobreescribir los métodos read y write. Sobreescribir cualquier otro método que pudieramos necesitar. Aseguranos de que los streams de entrada y salida funcionan juntos.

Esta sección nos muestra cómo implementar nuestros propios streams filtrados a través de un ejemplo que implementa una pareja de streams filtrados de entrada y salida. Tanto el stream de entrada como el de salida usan una clase checksum para calcular el checksum de los datos escritos o leídos desde el stream. El checksum se usa para determinar si los datos leídos por el stream de entrada corresponden con los datos escritos por el stream de salida. Cuatro clases y un interface componen este programa de ejemplo:
• • •

Las subclases de los streams de entrada y salida filtrados--CheckedOutputStream y CheckedInputStream. El interface Checksum y la clase Adler32 calculan un checksum para los streams. La clase CheckedIOTest define el método main para el programa.

La clase CheckedOutputStream La clase CheckedOutputStream es una subclase de FilterOutputStream que calcula un checksum sobre los datos que están siendo escritos en el stream. Cuando se crea un CheckedOutputStream, debemos usar su único constructor. public CheckedOutputStream(OutputStream out, Checksum cksum) { super(out); this.cksum = cksum; } Este constructor toma un argumento OutputStream y otro Checksum. El argumento OutputStream es el stream de salida que este CheckedOutputStream debe filtrar. El argumento Checksum es un objeto que calcula un checksum. CheckedOutputStream se autoinicializa llamando al constructor de su superclase e inicializando una variable privada , cksum, con el objeto Checksum. El CheckedOutputStream usa cksum para actualizar el checksum cada vez que se escribe un dato en el stream.

18

CheckedOutputStream necesita sobreescribir los métodos write de FilterOutputStream para que cada vez que se llame al método write, se actualice el checksum. FilterOutputStream define tres versiones del método write.

1. write(int i) 2. write(byte[] b) 3. write(byte[] b, int offset, int length)
CheckedOutputStream sobreescribe estos tres métodos. public void write(int b) throws IOException { out.write(b); cksum.update(b); } public void write(byte[] b) throws IOException { out.write(b, 0, b.length); cksum.update(b, 0, b.length); } public void write(byte[] b, int off, int len) throws IOException { out.write(b, off, len); cksum.update(b, off, len); } Las implementaciones de estos tres métodos write son correctas: escriben los datos en el stream de salida al que este stream está adjuntado, luego actualiza el checksum. La Clase CheckedInputStream La clase CheckedInputStream. CheckedInputStream es una subclase de FilterInputStream que calcula un checksum de los datos que están siendo leídos desde el stream. Cuando se crea un CheckedInputStream, debemos usar su único constructor: public CheckedInputStream(InputStream in, Checksum cksum) { super(in); this.cksum = cksum; } Este constructor es similar al de la clase CheckedOutputStream. Sólo que CheckedOutputStream necesita sobreescribir los métodos write de FilterOutputStream, CheckedInputStream debe sobreescribir los métodos read de FilterInputStream para que cada que vez que se llame al método read, se actualice el checksum. Como con FilterOutputStream, FilterInputStream define tres versiones del método read y CheckedInputStream las sobreescribe todas. public int read() throws IOException { int b = in.read(); if (b != -1) {

19

cksum.update(b); } return b; } public int read(byte[] b) throws IOException { int len; len = in.read(b, 0, b.length); if (len != -1) { cksum.update(b, 0, len); } return len; } public int read(byte[] b, int off, int len) throws IOException { len = in.read(b, off, len); if (len != -1) { cksum.update(b, off, len); } return len; } Las implementaciones de estos tres métodos read son correctas: leen los datos desde el stream de entrada al que está adjunto este stream filtrado; entonces si se leyó realmente algún dato, se actualiza el checksum. El Interface Checksum y la clase Adler32 El interface Checksum define cuatro métodos para que lo implementen los objetos checksum; estos métodos resetean, actualizan y devuelven el valor del checksum. Podríamos escribir una clase Checksum que calcule un tipo específico de checksum como el CRC-32. Observa que la herencia en el checksum es la noción de estado. El objeto checksum no sólo calcula un checksum y ya está. En vez de eso, el checksum es actualizado cada vez que se lee o se escribe información en el stream para el que este objeto calcula el checksum. Si queremos reutilizar un objeto checksum, debemos resetearlo. Para este ejemplo, hemos implementado el checksum Adler32, que es casi una versión del checksum CRC-32 pero puede ser calculado más rápidamente. Un Programa de Prueba La última clase del ejemplo, CheckedIOTest, contiene el método main para el programa. import java.io.*; public class CheckedIOTest { public static void main(String[] args) throws IOException { Adler32 inChecker = new Adler32(); Adler32 outChecker = new Adler32(); CheckedInputStream in = null;

20

CheckedOutputStream out = null; try { in = new CheckedInputStream( new FileInputStream("farrago.txt"), inChecker); out = new CheckedOutputStream( new FileOutputStream("outagain.txt"), outChecker); } catch (FileNotFoundException e) { System.err.println("CheckedIOTest: " + e); System.exit(-1); } catch (IOException e) { System.err.println("CheckedIOTest: " + e); System.exit(-1); } int c; while ((c = in.read()) != -1) out.write(c); System.out.println("Input stream check sum: " + inChecker.getValue()); System.out.println("Output stream check sum: " + outChecker.getValue()); in.close(); out.close(); } } El método main crea dos objetos Adler32, uno para un CheckedOutputStream y otro para un CheckedInputStream. El ejemplo requiere dos objetos checksum porque los objetos checksum se actualizan durante las llamadas a lo métodos read y write que ocurren concurrentemente. Luego, main abre un CheckedInputStream sobre un pequeño fichero de texto, farrago.txt, y un CheckedOutputStream sobre un fichero de salida llamado outagain.txt, que no existe hasta que ejecutemos el programa por primera vez. El método main lee el texto desde el CheckedInputStream y simplemente lo copia en el CheckedOutputStream. Los métodos read y write usan los objetos Adler32 para calcular un checksum durante la lectura y escritura. Después de haber leído completamente el fichero de entrada (y consecuentemente se haya completado de escribir el fichero de salida), el programa imprime los checksums de los dos streams (que deben ser iguales) y cierra los dos streams. Cuando ejecutemos CheckedIOTest, deberíamos ver esta salida: Input stream check sum: 736868089 Output stream check sum: 736868089

21

Filtrar Ficheros de Acceso Aleatorio Todos los streams filtrados de java.io descienden de InputStream o OutputStream, que implementan ficheros de acceso secuencial. Por eso si subclasificamos FilterInputStream o FilterOutputStream nuestros streams filtrados también serán ficheros de acceso secuencial. Escribir Filtros para Ficheros de Acceso Aleatorio, más adelante en esta lección nos muestra cómo re-escribir este ejemplo para que funcione sobre un RandomAccessFile también como sobre un DataInputStream o un DataOutputStream.

Serialización de Objetos

Serialización de Objetos El paquete java.io tiene otros dos streams de bytes-- ObjectInputStream ObjectOutputStream-- que funcionan como los otros streams de entrada y salida. Sin embargo, son especiales porque pueden leer y escribir objetos. La clave para escribir objetos es representar su estado de una forma serializada suficiente para reconstruir el objeto cuando es leído. Por eso, leer y escribir objetos es un proceso llamado serialización de objetos. La serialización de objetos es esencial para construir todo excepto las aplicaciones más temporales. Podemos usar la serialización de objetos de las siguientes formas:
• •

y

Invocación Remota de Métodos (RMI)--comunicación de objetos mediante sockets Persistencia de Peso Ligero-- el archivo de un objeto para una invocación posterior en el mismo programa.

Como programador Java, necesitamos conocer la serialización de objetos desde dos puntos de vista. Primero, necesitamos saber cómo serializar objetos escribiendolos a un ObjectOutputStream y luego leerlos de nuevo usando un ObjectInputStream. La siguiente página, Serializar Objetos, nos muestra cómo hacerlo. Segundo, querremos conocer como escribir una clase para que sus ejemplares puedan ser serializados. Podemos ver como se hace esto en la página: Proporcionar Serialización de Objetos para Nuestras Clases.

Serializar Objetos o ¿Cómo Escribir en un ObjectOutputStream? o ¿Cómo Leer desde un ObjectInputStream?

Serializar Objetos Reconstruir un objeto desde un stream requier primero que el objeto se haya escrito en un stream. Por eso empezaremos por aquí:

22

¿Cómo Escribir en un ObjectOutputStream? Escribir objetos a un stream es un proceso sencillo. Por ejemplo, aquí obtenemos la hora actual en milisegundos construyendo un objeto Date y luego serializamos ese objeto. FileOutputStream out = new FileOutputStream("theTime"); ObjectOutputStream s = new ObjectOutputStream(out); s.writeObject("Today"); s.writeObject(new Date()); s.flush(); ObjectOutputStream es un stream de proceso, por eso debe construirse sobre otro stream. Este código construye un ObjectOutputStream sobre un FileOutputStream, para serializar el objeto a un fichero llamado theTime. Luego, el string Today y un objeto Date se escriben en el stream con el método writeObject de ObjectOutputStream. Si un objeto se refiere a otro objeto, entonces todos los objetos que son alcanzables desde el primero deben ser escritos al mismo tiempo para poder mantener la relación entre ellos. Así, el método writeObject serializa el objeto especificado, sigue sus referencias a otros objetos recursivamente, y también los escribe todos. El stream ObjectOutputStream implementa el interface DataOutput que define muchos métodos para escribir tipos de datos primitivos, como writeInt, writeFloat, o writeUTF. Podemos usar estos métodos para escribir tipos de datos primitivos a un ObjectOutputStream. El método writeObject lanza una NotSerializableException si el objeto dado no es serializable. Un objeto es serializable sólo si clase implementa el interface Serializable. ¿Cómo Leer desde un ObjectInputStream? Una vez que hemos escrito objetos y tipos de datos primitivos en un stream, querremos leerlos de nuevo y reconstruir los objetos. Esto también es sencillo. Aquí está el código que lee el String y el objeto Date que se escribieron en el fichero llamado theTime del último ejemplo. FileInputStream in = new FileInputStream("theTime"); ObjectInputStream s = new ObjectInputStream(in); String today = (String)s.readObject(); Date date = (Date)s.readObject(); Cómo ObjectOutputStream, ObjectInputStream debe construirse sobre otro stream. En este ejemplo, los objetos fueros archivados en un fichero, por eso el código construye un ObjectInputStream sobre un FileInputStream. Luego, el código usa el método readObject de ObjectInputStream para leer el String y el objeto Date desde el fichero. Los objetos deben ser leídos desde el stream en el mismo orden en que se esribieron. Observa que el valor de retorno de readObject es un objeto que es forzado y asignado a un tipo específico.

23

El método readObject des-serializa el siguiente objeto en el stream y revisa sus referencias a otros objetos recursivamente para des-serializar todos los objetos que son alcanzables desde él. De esta forma, mantiene la relación entre los objetos. El stream ObjectInputStream implementa el interface DataInput que define métodos para leer tipos de datos primitivos. Los métodos de DataInput son paralelos a los definidos en DataOutput para escribir tipos de datos primitivos. Entre ellos se incluyen readInt, readFloat, y readUTF. Se usan estos métodos para leer tipos de datos primitivos desde un ObjectInputStream.

Proporcionar Serialización de Objetos para Nuestras Clases o Implementar el Interface Serializable o Personalizar la Serialización o Implementar el Interface Externalizable o Proteger la Información Sensible

Proporcionar Serialización de Objetos para Nuestras Clases Un objeto es serializable sólo si su clase implementa el interface Serializable. Así, si queremos serializar un ejemplar de una de nuestras clases, la clase debe implementar este interface. Las buenas noticias es que Serializable es un interface vacío. Es decir, no contiene ninguna declaración de método; su propósito es simplemente identificar las clases cuyos objetos son serializables. Implementar el Interface Serializable Aquí tenemos la definición completa del interface Serializable: package java.io; public interface Serializable { // there's nothing in here! }; Crear ejemplares de una clase serializable es fácil. Sólo hay que añadir la claúsula implements Serializable a la declaración de nuestra clase: public class MySerializableClass implements Serializable { ... } No tenemos que escribir ningún método. La serialización de un ejemplar de esta clase la maneja el método defaultWriteObject de ObjectOutputStream. Este método escribe cualquier cosa necesaria para reconstruir un ejemplar de la clase, incluyendo lo siguiente:
• • •

La clase del Objeto La firma de la clase Los valores para todos los miembros no-transient y no-static, incluyendo los miembros que se refieren a otros objetos.

24

Para muchas clases, este comportamiento por defecto es suficiente. Sin embargo, la serialización por defecto puede ser lenta, y las clases podrían querer un control más explicito sobre la serialización. Personalizar la Serialización Podemos personalizar la serialización de nuestras clases proporcionando dos métodos para ella: writeObject y readObject. El método writeObject controla la información que se graba. Normalmente se usa para añadir información adicional al stream. El método readObject lee la información escrita por el correspondiente método writeObject o puede usarse para actualizar el estado del objeto después de haber sido restaurado. El método writeObject debe declarse exactamente como se muestra en el siguiente ejemplo. Lo primero que debe hacer es llamar al método defaultWriteObject para realizar la serialización por defecto. Cualquier ajuste puede realizarse después. private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); // customized serialization code } El método readObject debe leer todo lo escrito por writeObject en el mismo orden en que se escribió. El método readObject también puede realizar cálculos o actualizar el estado del objeto de alguna forma. Aquí está el método readObject que corresponde al método writeObject anterior: private void readObject(ObjectInputStream s) throws IOException { s.defaultReadObject(); // customized deserialization code ... // followed by code to update the object, if necessary } El método readObject debe declarse exactamente como se ha mostrado. Los métodos writeObject y readObject son responsalbes de serializar sólo las clases inmediatas. Cualquier serialización requerida por la superclase se maneja automáticamente. Sin embargo, una clase que necesita coordinarse explícitamente con su superclase para serializarse puede hacerlo implementando el interface Externalizable. Implementar el Interface Externalizable Para un completo control explícito del proceso de serialización, una clase debe implementar el interface Externalizable. Para los objetos Externalizable sólo la identidad de la clase del objeto es grabada automáticamente en el stream. La clase es responsable de escribir y leer sus contenidos, y debe estar coordinada con su superclase para hacerlo.

25

Aquí tenemos una definición completa del interface Externalizable que desciende del interface Serializable: package java.io; public interface Externalizable extends Serializable { public void writeExternal(ObjectOutput out) throws IOException; public void readExternal(ObjectInput in) throws IOException, java.lang.ClassNotFoundException; } Lo siguiente sirve para una clase Externalizable:
• • • •

Debe implementar el interface java.io.Externalizable. Debe implementar un método writeExternal para salvar el estado del objeto. También, debe coordinarse explícitamente con sus superclase para salvar su estado. Debe implementar el método readExternal para leer los datos escritos por el método writeExternal desde el stream y restaurar el estado del objeto. Debe coordinarse explíctamente con su superclase para restaurar sus estado. Si se están escribiendo formatos definidos externamente, los métodos writeExternal y readExternal son los únicos responsables de esos formatos.

Los métodos writeExternal y readExternal son públicos y corren el riesgo de que un cliente pueda escribir o leer información en el objeto distinto usando sus métodos y campos. Estos métodos deben se usarse solamente cuando la información contenida en el objeto no sea importante o cuando exponer dicha información no represente un riesgo de seguridad. Proteger la Información Sensible Cuando desarrollamos una clase que proporcione acceso controlado a recursos, debemos tener cidado de proteger la información y las funciones sensibles. Durante la desserialización, se restaura el estado privado del objeto. Por ejemplo, un descriptor de fichero contiene un manejador que propociona acceso a un recurso del sistema operativo. Siendo posible olvidar que un descriptor de fichero puede permitir ciertas formas de accesos ilegales, ya que la restauración del estado se hace desde un stream. Por lo tanto en el momento de la serialización se debe tener cuidado y no creer que el stream contiene sólo representaciones válidas de objetos. Para evitar comprometer una clase, debemos evitar que el estado sensible de un objeto sea restaurado desde un stream o que sea reverificado por la clase. Hay disponibles varias técnicas para proteger los datos sensibles. La más sencilla es marcar los campos que contienen los datos sensibles como private transient. Los campos transient y static no son serializados. Marcando el campo evitaremos que el estado aparezca en el stream y sea restaurado durante la des-serialización. Como la lectura y escritura (de campos privados) no puede hacerde desde fuera de la clase, los campos transient de la clase son seguros.

26

Las clases particularmente sensibles no debe ser serializadas. Para conseguir esto, el objeto no debe implementar ninguno de los interfaces Serializable ni Externalizable. Algunas clases podrían encontrar beneficioso permitir la escritura y lectura pero específicamente manejadas y revalidar el estado cuando es des-serializado. La clase debería implementar los métodos writeObject y readObject para salvar y recuperar sólo el estado apropiado. Si el acceso debe ser denegado, lanzar una NotSerializableException evitará accesos posteriores.

Trabajar con Ficheros de Acceso Aleatorio o Usar Ficheros de Acceso Aleatorio o Escribir Filtros para Ficheros de Acceso Aleatorio

Trabajar con Ficheros de Acceso Aleatorio Hasta ahora los streams de entrada y salida de esta lección han sido streams de acceso secuencial, streams cuyo contenido debe ser leído o escrito secuencialmente. A pesar de su increible utilidad, los ficheros de acceso secuencial son una consecuencia de un medio secuencial como una cinta magnética. Los ficheros de acceso aleatorio, por otro lado, permiten acceso no secuencial, o aleatorio, a los contenidos de un fichero. Pero por qué necesitamos ficheros de acceso aleatorio. Consideremos el formato de archivo conocido como "zip". Los archivos Zip contienen ficheros que normalmente están comprimidos para ahorrar espacio. Los archivos Zip también contienen al final un directorio de entradas que indica donde empiezan los distintos ficheros contenidos enel archivo Zip.

Supongamos que queremos extraer un fichero específico de un archivo Zip. Si usamos un stream de acceso secuencial, tenemos que hacer lo siguiente:
• • • •

Abrir el fichero Zip. Buscar a través del fichero Zip hasta localizar el fichero que queremos extraer. Extraer el fichero. Cerrar el archivo Zip.

Como media, usando este algoritmo, tendríamos que leer la mitad del archivo Zip antes de encontrar el fichero que queremos extraer. Podemos extraer el mismo fichero del archivo Zip de forma más eficiente usando la característica "seek" de un fichero de acceso aleatorio:
• • • •

Abrimos el fichero Zip. Saltamos al directorio de entradas y localizamos la entrada del fichero que queremos extraer del archivo Zip. Saltamos (hacia atrás) dentro del archivo Zip a la posición del fichero a extraer. Extraemos el fichero.

27

Cerramos el archivo Zip.

Este algoritmo es más eficiente porque sólo tenemos que leer el directorio de entradas y el fichero que queremos extraer. La clase RandomAccessFile del paquete java.io implementa ficheros de acceso aleatorio.

Usar Ficheros de Acceso Aleatorio

Usar Ficheros de Acceso Aleatorio La clase RandomAccessFile implementa los interfaces DataInput y DataOutput y por lo tanto puede usarse para leer y escribir. RandomAccessFile es similar a FileInputStream y FileOutputStream en que especificamos un fichero del sistema de ficheros nativo para abrirlo cuando lo creamos. Podemos hacer esto con un nombre de fichero o un objeto File. Cuando creamos un RandomAccessFile, debemos indicar si sólo queremos leer o también queremos escribir en el fichero. (tenemos que poder leer un fichero para poder escribirlo). La siguiente línea de código cea un RandomAccessFile que lee el fichero llamado farrago.txt: new RandomAccessFile("farrago.txt", "r"); Y esta abre el mismo fichero tanto para lectura como para escritura: new RandomAccessFile("farrago.txt", "rw"); Después de haber abierto el fichero, podemos usar los métodos comunes readXXX o writeXXX para realizar I/O en el fichero. RandomAccessFile soporta la noción de puntero de fichero. Este puntero indica la posición actual en el fichero, cuando el fichero se crea por primera ver, el puntero de fichero es 0, indicando el principio del fichero. Las llamadas a los métodos readXXX y writeXXX ajustan la posición del puntero de fichero según el número de bytes leídos o escritos.

Además de los métodos de I/O normales que implícitamente mueven el puntero de fichero cuando ocurre la operación, RandomAccessFile contiene tres métodos que manipulan explícitamente el puntero de fichero: skipBytes Mueve el puntero hacia adelante el número de bytes especificado. seek Posiciona el puntero de fichero en la posición anterior al byte especificado. getFilePointer Devuelve la posición actual (byte) del puntero de fichero.

28

Escribir Filtros para Ficheros de Acceso Aleatorio o CheckedDataOutput contra CheckedOutputStream o CheckedDataInput contra CheckedInputStream o Los Programas Principales

Escribir Filtros para Ficheros de Acceso Aleatorio Reescribamos el ejemplo de Escribir Nuestros Propios Streams Filtrados para que funcione sobre RandomAccessFiles. Como RandomAccessFile implementa los interfaces DataInput y DataOutput, un beneficio lateral es que los streams filtrados también funcionan con otros streams DataInput y DataOutput incluidos algunos streams de acceso secuencial como DataInputStream y DataOutputStream. El ejemplo CheckedIOTest de Escribir Nuestros Propios Streams Filtrados implementa dos streams filtrados, CheckedInputStream y CheckedOutputStream, que calculan un checksum de los datos que son leídos o escritos en el stream. El nuevo ejemplo, CheckedDataOutput es una re-escritura de CheckedOutputStream-calcula un checksum para los datos escritos en el stream-- pero opera sobre objetos DataOutput en lugar de sobre objetos OutputStream. De forma similar CheckedDataInput modifica CheckedInputStream para que ahora funciona sobre objetos DataInput en lugar de objetos InputStream. CheckedDataOutput contra CheckedOutputStream Echemos un vistazo a las diferencias entre CheckedDataOutput y CheckedOutputStream. La primera diferencia es que CheckedDataOutput no desciende de FilterOutputStream. En su lugar, implementa el interface DataOutput. public class CheckedDataOutput implements DataOutput Nota: Para mantener el ejemplo sencillo, la clase CheckedDataOutput realmente proporcionada en esta sección no está declarada para implementar DataOutput, porque el interface DataOutput especifica demasiados métodos. Sin embargo, la clase CheckedDataOutput proporcionada en el ejemplo implementa varios métodos de DataOutput para ilustrar como deberían funcionar. Luego, CheckedDataOutput declara una variable privada para contener un objeto DataOutput. private DataOutput out; Este es el objeto en el se escribirán los datos. El constructor para CheckedDataOutput es diferente del constructor de CheckedOutputStream: CheckedDataOutput se crea sobre un objeto DataOutput en vez sobre un objeto OutputStream. public CheckedDataOutput(DataOutput out, Checksum cksum) { this.cksum = cksum;

29

this.out = out; } Este constructor no llama a super(out) como hacía el constructor de CheckedOutputStream. Esto es porque CheckedDataOutput desciende de Object en vez de la clase stream. Aquí están las únicas modificaciones echas a CheckedOutputStream para crear un filtro que funcione sobre objetos DataOutput. CheckedDataInput contra CheckedInputStream CheckedDataInput requiere los mismos cambios que CheckedDataOuput.

CheckedDataInput no desciende de FilterInputStream pero implementa el interface DataInput.

Nota: Para mantener el ejemplo sencillo, la clase CheckedDataInput realmente proporcionada en esta sección no está declarada para implementar DataInput, porque el interface DataInput especifica demasiados métodos. Sin embargo, la clase CheckedDataInput proporcionada en el ejemplo implementa varios métodos de DataInput para ilustrar como deberían funcionar.
• •

CheckedDataInput declara una variable privada para contener un objeto DataInput que lo envuelve. El constructor de CheckedDataInput requiere un objeto DataInput en vez de un objeto InputStream.

Además de estos cambios, el método read también se ha modificado. El CheckedInputStream del ejemplo original implementa dos métodos read, uno para leer un sólo byte y otro para leer un array de bytes. El interface DataInput teine métodos que implementan la misma funcionalidad, pero tienen diferentes nombres y firmas de métodos. Así el método read de la clase CheckedDataInput tiene nuevos nombres y firmas de métodos. public byte readByte() throws IOException { byte b = in.readByte(); cksum.update(b); return b; } public void readFully(byte[] b) throws IOException { in.readFully(b, 0, b.length); cksum.update(b, 0, b.length); } public void readFully(byte[] b, int off, int len) throws IOException { in.readFully(b, off, len); cksum.update(b, off, len);

30

} Los Programas Principales Finalmente, este ejemplo tiene dos programas principales para probar los nuevos filtros. CheckedDITest, que ejecuta el filtro sobre ficheros de acceso secuencial (objetos DataInputStream y DataOutputStream), y CheckedRAFTest, que ejecuta los filtros sobre ficheros de acceso aleatorio (objetos RandomAccessFiles). Estos dos programas se diferencian sólo en el tipo del objeto que abren para el filtro. CheckedDITest crea un DataInputStream y un DataOutputStream y usa el filtro checksum sobre ellos, como en el siguiente código: cis = new CheckedDataInput( new DataInputStream(new FileInputStream("farrago.txt")), inChecker); cos = new CheckedDataOutput( new DataOutputStream(new FileOutputStream("outagain.txt")), outChecker); CheckedRAFTest crea dos RandomAccessFiles, uno para leer y uno para escribir, y usa el filtro checksum sobre ellos. cis = new CheckedDataInput( new RandomAccessFile("farrago.txt", "r"), inChecker); cos = new CheckedDataOutput( new RandomAccessFile("outagain.txt", "rw"), outChecker); Cuando ejecutemos cualquiera de estos programas deberíamos ver la siguiente salida: Input stream check sum: 736868089 Output stream check sum: 736868089

Y el Resto...

Y el Resto... Además de las clases e interfaces explicadas en esta lección, java.io contiene las siguientes clases e interfaces File Representa un fichero del sistema de ficheros nativo.

31

Podemos crear un objeto File para un fichero del sistema de ficheros nativo y luego consultar en el objeto información sobre ese fichero (como su path completo). FileDescriptor Representa un manejador de fichero (o descriptor) para abrir un fichero o un socket. Normalmente no usaremos esta clase. StreamTokenizer Parte el contenido de un stream en tokens. Los Tokens son la unidad más pequeña reconocida por un algoritmo de análisis de texto (como palabras, símbolos, etc). Se puede usar un StreamTokenizer para analizar un fichero de texto. Por ejemplo, podríamos usarlo para dividir un fichero fuente Java en nombres de variables, operadores, etc, o dividir un fichero HTML en etiquetas HTML. FilenameFilter Usado por el método list de la clase File para determinar qué ficheros se deben mostrar de un directorio. El FilenameFilter accepta o rechaza ficheros basándose en su nombre. Podríamos usar FilenameFilter para implementar unos sencillos patrones de búsqueda de ficheros como foo*. También podemos encontrar otros streams de entrada y salida en el paquete java.util.zip, incluyendo estos: CheckedInputStream y CheckedOutputStream Una pareja de streams de entrada y salida que mantiene un checksum de los datos que están siendo leídos o escritos. DeflaterOutputStreamy InflaterInputStream Comprime o descomprime los datos que están siendo leídos o escritos. GZIPInputStream y GZIPOutputStream Lee y escribe datos comprimidos en el formato GZIP. ZipInputStream y ZipOutputStream Lee y escribe datos comprimidos en el formato ZIP.

32

You're Reading a Free Preview

Descarga
scribd
/*********** DO NOT ALTER ANYTHING BELOW THIS LINE ! ************/ var s_code=s.t();if(s_code)document.write(s_code)//-->