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? O bjectInputStream? Proporcionar Serialización de Objetos para par a 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 información a una fuente externa. La inform información ació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
Escribir
abrir un stream mientras haya información leer información cerrar el stream
abrir un stream mientras haya información escribir información cerrar el stream
El paquete java.io 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 Reader y Writer Writer son son las superclases abstractas para streams de caracteres en java.io en java.io..
3
Reader pro propor porcio ciona na el API y una imp implem lement entaci ación ón para para readers-- st stream reams s que leen caracteres carac teres de 16-bit 16-bits-s-- y Writer proporciona el API y una implementación para writers-streams que escriben caracteres de 16-bits. Las subclases de Reader Reader y y Writer Writer que 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 Reader y 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,, pa OutputStream para ra lee eerr y esc scri ribi birr by byte tes s de 88-bi bits ts.. InputStream y OutputStream proporc prop orcion ionan an el API y alg alguna una imp implem lement entaci ación ón st stream reams s de ent entrad rada a y sal salida ida y lee leen n 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 Reader y y Writer , las subclases de InputStream y OutputStream proporcionan I/O especializada especializada que se divid divide e en dos cate categorias gorias:: streams de profun profundidad didad y strea streams ms 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. ObjectOutputStream Esta clases se cubren completamente en la página Serialización de Objetos. Objetos. Entender las Superclases de I/O
Reader y InputStream definen APIs similares pero para diferentes tipos de datos. Por ejemplo, Reader Reader contiene 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 simi similarmen larmente te paral paralelas. elas. Writer def define ine tre tres s mét método odos s par para a 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 est Todos estos os str stream eams s --re --reade aders, rs, wri writer ters, s, inp inputs utstre treams ams,, y out output putstr stream eams-s-- se abren abren automáticam automá ticamente ente cuand cuando o se crean. Podemos cerra cerrarr explí explícitam citamente ente cualq cualquier uier strea stream m llamando a su método close 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 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:: java.io Tipo Tip o de Sink Sink Str Stream eams s de Carac Caracter teres es Str Stream eams s de Bytes Bytes
Memory
Pipe File
CharArrayReader , CharArrayWriter , StringReader , StringWriter PipedReader , PipedWriter FileReader , FileWriter
ByteArrayInputStream, ByteArrayInputStream, ByteArrayOutputStream,, ByteArrayOutputStream StringBufferInputStream PipedInputStream, PipedInputStream, PipedOutputStream FileInputStream,, FileInputStream 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 ByteArrayInputStream y ByteArrayOutputStream
CharArrayWriter
Estos streams streams se usan para leer y escri escribir bir desde memor memoria. ia. 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 Colectivam Colec tivamente ente llamados streams 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 FileReader y y FileWriter para copiar el contenido de un fichror a otro. PipedReader y PipedInputStream y PipedOutputStream
PipedWriter
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.. Streams StringReader StringBufferInputStream
y
StringWriter
Se usa StringReader StringReader para para leer caracteres desde un String que reside en memoria. Se usa StringWriter StringWriter para para escribir en un String String.. StringWriter recoge los caracteres escritos en un StringBuffer , que puede ser conv co nver erti tido do en un String String.. StringBufferInputStream es si simi mila larr 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 --FileReader , FileWriter , FileInputStream FileInputStream,, y FileOutputStream 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 File,, o desde un objeto FileDescriptor . El sigui siguiente ente programa Copy usa FileReader FileReader y y FileWriter para copiar el contenido de un fichero llamado farrago.txt en otro fichero llamado outagain.txt 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 FileReader sobre sobre farrago.txt y abre FileWriter FileWriter sobre sobre outagain.txt 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. java.io. Este programa usa este objeto sólo para construir un FileReader FileReader sobre sobre farrago.txt farrago.txt.. Sin embargo, se podría usar inputFile usar inputFile para obtener información sobre farrago.txt farrago.txt,, como su path completo. Después de haber ejecutado el programa, deberíamos encontrar una copia exacta de farrago.txt en un fi fich chero ero llllam amad ado o outagain.txt en el mi mism smo o di dire rect ctor orio io.. Aq Aquí uí es está tá 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 FileReader y y FileWriter FileWriter leen 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 carac teres por defec defecto. to. Podem Podemos os encon encontrar trar la codif codificaci icación ón de caract caracteres eres por defec defecto to usando System.getProperty("file.encoding") System.getProperty("file.encoding").. Pa Para ra es espe peci cifi fic car ot otra ra cod odif ifiica cac ció ión, n, deberíamos construir un OutputStreamWriter OutputStreamWriter sobre sobre un FileOutputStream y especificarla. Para Pa ra má más s in info form rmac ació ión n so sobr bre e la co codi difi fica caci ción ón de ca cara ract cter eres es pu pued edes es ve verr la se secc cció ión n Internationalization.. Internationalization Para cu Para curi rios osos os,, aq aquí uí te tene nemo mos s ot otra ra ve vers rsió ión n de es este te pr prog ogram rama, a, CopyBytes CopyBytes,, qu que e us usa a FileInputStream y FileOutputStream en lugar de FileReader FileReader y y FileWriter . •
Como usar Piped Streams o Usar Streams para Envolver otros Streams
Como usar Piped Streams 8
PipedReader y PipedWriter (y su sus s co corr rres espo pond ndie ient ntes es st strea reams ms de en entr trad ada a y sa salilida da PipedInputStream y PipedOutputStream PipedOutputStream)) imp implem lement entan an los com compon ponent entes es de ent entrad rada a 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 puedie ra ser usada como entrada del otro para que pudie pudieramos ramos encadenar encadenar una seri serie e 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 investigaremo investigaremos s un progra programa ma que imple implementa menta lo que representa el diagrama de la figura anterior. Este programa usa PipedReader PipedReader y y PipedWriter PipedWriter para 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 compon com pone e de var varias ias cla clases ses.. Est Esta a sec secció ción n mue muestr stra a y exp explic lica a sól sólo o los ele elemen mentos tos 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 RhymingWords.. FileReader words = new FileReader("words.txt"); Reader rhymingWords = reverse(sort(reverse(words)));
9
La llamada más interna a reverse toma un FileReader FileReader abierto abierto sobre el fichero words.txt que contiene una lista de palabras. El valor devuelto por reverse se pasa a sort sort,, cuyo valor de retorno es pasado a otra llamada a reverse reverse.. Echemos un vistazo al método reverse reverse;; el métod método o sort es similiar y lo entenderemos una vez que comprendamos reverse 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 PipedWriter puede 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 PipedWriter y y devuelve el PipedReader al 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 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 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 source.. El pr prog ogra rama ma ha hace ce es esto to pa para ra po pode derr us usar ar el mé méto todo do de co conv nven enie ienc ncia ia 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 . Frecuente Frecue ntemen mente te ver veremo emos s est estrea reams ms env envuel ueltos tos de est esta a for forma ma par para a así com combin binar ar 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 codifi cod ificac cación ión de car caract actere eres, s, mie mientr ntras as lee leen n o esc escrib riben. en. Al igu igual al que de los stream stream de profundidad, java.io 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 de java.io:: Proceso
Almacenamiento Filtrado Con onve vers rsiión entr en tre e Bytes y Caracteres Concatenación Serialización de Objetos
Stream de Caracteres Streams de Bytes
BufferedReader , BufferedWriter FilterReader , FilterWriter InputStreamReader , OutputStreamWriter
Conversión de Datos Contaje Exploración Impresión
LineN Line Num umbe berR rRea eade derr PushbackReader PrintWriter
BufferedInputStream, BufferedInputStream, BufferedOutputStream FilterInputStream,, FilterInputStream FilterOutputStream SequenceInputStream ObjectInputStream,, ObjectInputStream ObjectOutputStream DataInputStream,, DataInputStream DataOutputStream Lin ineN eNum umbe berI rInp nput utSt Stre ream am PushbackInputStream 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 BufferedReader y BufferedWriter BufferedWriter BufferedInputStream BufferedInputStream y BufferedOutputStream
Almacenan los datos en buffer Almacenan buffers s mientras leen o escri escriben, ben, por lo tanto reduc reduciendo iendo 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 FilterReader y FilterWriter FilterWriter FilterInputStream 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 InputStreamReader y OutputStreamWriter
Una pareja de reader y writer que realiza un puente entre streams de bytes y streams de caracteres. Un InputStreamReader InputStreamReader lee 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 OutputStreamWriter convierte convierte caracteres a bytes usando una decodi dec odific ficaci ación ón de car caract actere eres s por def defect ecto o o una dec decodi odific ficaci ación ón de car caract actere eres s especificada por su nombre y luego escribe estos bytes en un OutputStream OutputStream.. Podemo Pode mos s ap apre rend nder er el no nomb mbre re de la co codi difi fica caci ción ón de ca cara ract cter eres es po porr de defe fect cto o llamando a System.getProperty("file.encoding") 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 ver Serialización de Objetos. 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 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 Alguna s vec veces, es, cua cuando ndo se lee leen n dat datos os des desde de un st stream ream,, enc encont ontrar raremo emos s úti útill 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 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 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 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); System.err.println("ListOfFiles } } return in; } } ListOfFiles ListOfFile s imple implementa menta el inter interface face Enumeration Enumeration.. Ve Vere remo mos s co como mo es esto to en entr tra a en ju jueg ego o cuando pasemos por el resto del programa. Después el método main crea el SequenceInputStream 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 par para a obe obeten tener er el sig siguie uiente nte InputStream InputStream.. ListOfFiles crea objeto objetos s FileInputStream enforma de lazo, lo que significa que siempre que SequenceInputStream llama a nextElement 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.
todos
los
datos
leidos
desde
el
Prueba Esto:
Intenta ejecutar Concatenate ejecutar Concatenate sobre los ficheros farrago.txt y words.txt que han sido usados como entradas para otros ejemplos de esta lección.
•
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 pa paqu quet ete e java.io con onti tien ene e es esto tos s FilterInputStream o FilterOutputStream FilterOutputStream.. • • • • •
str trea eams ms fi filt ltra rado dos s
que qu e
son so n
sub ubcl clas ases es
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 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 readLine por DataInputStream.. Cómo usar DataInputStream y DataOutputStream
Esta página cubre y explica un ejemplo de uso de DataInputStream y DataOutputStream DataOutputStream,, dos streams filtrados que pueden leer y escribir tipos de datos primitivos de Java. Escribir Nuestros Propios Streams Filtrados
Muchos pro Muchos progra gramad madore ores s pod podría rían n enc encont ontrar rar que nec necesi esitan tan imp implem lement entar ar sus pro propio pios s 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 fi fillas y col olum umna nas s. Fre recu cuen ente tem men ente te,, es esto tos s pr prog ogra rama mado dore res s, su subc bcla las sif ifiica can n FilterOutputStream y FilterInputStream pa para ra co cons nseg egui uirr su sus s ob obje jeti tivo vos. s. Es Esta ta se secc cció ión 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ág Este página ina mue muestr stra a cóm cómo o usa usarr las cla clases ses DataInputStream DataInputStreamy y DataOutputStream de java.io.. Crea un ejemplo: DataIOTest java.io 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 DataOutputStream, otro OutputStream OutputStream.. En este caso, se adjunta a un FileOutputStream que está configurado para escribir en un fichero llamado invoice1.txt invoice1.txt.. DataOutputStream dos = new DataOutputStream( new FileOutputStream("invoice1.txt")); Luego, DataIOTest usa métod métodos os espec especializa ializados dos writeXXX writeXXXde de 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 tam tambié bién n deb debe e adj adjunt untar ar alg algún ún otr otro o InputStream InputStream;; en es este te cas aso, o, un FileInputStream configurado para leer el fichero que acaba de escribir --invoice1.txt --invoice1.txt.. DataIOTest luego sólo lee los datos usando los métodos especializados readXXX de DataInputStream:: 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 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 porq ue cua cualqu lquier ier val valor or dev devuel uelto to par para a ind indica icarr fin fin-de -de-fi -fiche chero ro pod podría ría ser un val valor or leí leído 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 readDouble,, readInt readInt,, o uno de los otros métodos de lectura que leen números. Por eso el método readXXX readXXXde de DataInputStream lanza una EOFException en su lugar. Cuando ocurre EOFException termina el while (true). (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 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 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 ch chec ecks ksum um se us usa a pa para ra de dete term rmin inar ar si lo los s da dato tos s le leíd ídos os po porr el st stre ream am de en entr trad ada a 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 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. CheckedOutputStream public CheckedOutputStream(OutputStream out, Checksum cksum) { super(out); this.cksum = cksum; } Este constructor toma un argumento OutputStream y otro Checksum Checksum.. El argumento OutputStream es el stream de salida que este CheckedOutputStream debe filt fi ltra rar. r. El ar argu gume ment nto o Checksum es un objeto que calcula un checksum. CheckedOutputStream se aut autoin oinici iciali aliza za lla llaman mando do al con constr struct uctor or de su sup superc erclas lase e e inic in icia iali liz zan ando do un una a va vari riab ablle pr priiva vada da , cksum cksum,, con el objeto Checksum Checksum.. El CheckedOutputStream usa cksum para actualizar el checksum cada vez que se escribe un dato en el stream.
18
CheckedOutputStream neces necesita ita sobree sobreescrib scribir ir los méto métodos dos write de FilterOutputStream para que cada vez que se llame al método write write,, se ac actu tual alic ice e el ch chec ecks ksum um.. FilterOutputStream define tres versiones del método write write.. 1. 2. 3.
write(int i) write(byte[] b) 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.. CheckedInputStream es una subclase de FilterInputStream que calcula un checksum de los datos que están siendo leídos desde el stream. Cuando se cre rea a un CheckedInputStream,, debemos usar su único constructor: CheckedInputStream public CheckedInputStream(InputStream in, Checksum cksum) { super(in); this.cksum = cksum; } Este constructor es similar al de la clase CheckedOutputStream CheckedOutputStream.. Sólo qu Sólo que e CheckedOutputStream ne nece cesi sita ta so sobre brees escr crib ibir ir lo los s mé méto todo dos s write de FilterOutputStream,, CheckedInputStream de FilterOutputStream debe be so sobr bree eesc scri ribi birr lo los s mé méto todo dos s read de FilterInputStream para que cada que vez que se llame al método read read,, se actualice el checksum. check sum. Como con FilterOutputStream FilterOutputStream,, FilterInputStream def define ine tre tres s ve versi rsione ones s 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 inter interface face Checksum def define ine cua cuatro tro mé métod todos os par para a que lo imp implem lement enten en los obj objeto etos s checks che cksum; um; est estos os mét método odos s res resete etean, an, act actual ualiza izan n y dev devuel uelven ven el val valor or del che checks cksum. um. Podríamos escribir una clase Checksum que calcule un tipo específico de checksum como el CRC-32. Observa que la herencia en el check Observa checksum sum es la noció noción n de estad estado. o. El objet objeto o 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 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 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 Adler32,, uno para un CheckedOutputStream y otro para un CheckedInputStream 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 farrago.txt,, y un CheckedOutputStream sobre un fichero de salida llamado outagain.txt 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 objet CheckedOutputStream objetos os 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 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 OutputStream,, que implementan ficheros de acceso secuencial. Por eso si subclasificamos FilterInputStream o FilterOutputStream nue nuestr stros os str stream eams s fil filtra trados dos tam tambié bién n ser serán án fic ficher heros os de acc acceso eso secuencial. Escribir Filtros para Ficheros de Acceso Aleatorio, 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 DataOutputStream.. •
Serialización de Objetos
Serialización de Objetos
El pa paqu quet ete e java.io ti tien ene e ot otro ros s do dos s stre ream ams s de by byte tess--- ObjectInputStream y ObjectOutputStream-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 serial serializaci ización ón de objeto objetos s es esencial para const construir ruir todo excep excepto to las aplic aplicacione aciones s más temporales. Podemos usar la serialización de objetos de las siguientes formas: • •
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 ObjectInputStream.. La siguiente página, Serializar Objetos, 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. Clases . •
Serializar Objetos o ¿Cómo Escribir en un ObjectOutputStream? o ¿Cómo Leer desde un ObjectInputStream? O bjectInputStream?
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 str stream eam de pro proces ceso, o, por eso debe con constr struir uirse se sob sobre re otr otro o stream. strea m. Este códig código o const construye ruye un ObjectOutputStream sobre un FileOutputStream FileOutputStream,, para serializar el objeto a un fichero llamado theTime theTime.. Luego, el str Luego, string ing Today y un ob obje jeto to Date se es escr crib iben en en el st stre ream am co con n el mé méto todo do writeObject de ObjectOutputStream 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 entr en tre e el ello los. s. As Así, í, el mé méto todo do writeObject ser serial ializa iza el obj objeto eto esp especi ecific ficado ado,, sig sigue ue 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 writeInt,, writeFloat writeFloat,, o writeUTF writeUTF.. Pode Po demo mos s usa sarr es esto tos s mét étod odos os pa para ra es esc cri ribi birr ti tipo pos s de da dato tos s pr priimi miti tivo vos s a un ObjectOutputStream.. ObjectOutputStream El mét método odo writeObject lan lanza za una NotSerializableException si el ob obje jeto to da dado do no es serializable. Un objeto es serializable sólo si clase implementa el interface Serializable 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 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 FileInputStream.. Luego, el códig código o 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 val or de ret retorno orno de readObject es un objeto que es forzado y asignado a un tipo específico.
23
El mét método odo readObject de dess-se seri rial aliz iza a el si sigu guie ient nte e ob obje jeto to en el st strea ream m y re revi visa sa su sus s referencias refere ncias a otros objetos recursivamente recursivamente para des-s des-serial erializar izar 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 paralelos a los definidos definidos en DataOutput par para a esc escrib ribir ir tip tipos os de dat datos os pri primit mitivo ivos. s. Ent Entre re ell ellos os se inc incluy luyen en readInt readInt,, readFloat,, y readUTF readFloat readUTF.. Se usan estos métodos para leer tipos de datos primitivos desde un ObjectInputStream.. ObjectInputStream •
Proporcionar Serialización de Objetos para par a 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 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 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 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 val valore ores s par para a tod todos os los mie miembr mbros os nono-transient transient y no-static no-static,, inc incluy luyend endo o 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 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ét método odo writeObject debe declarse exactamente como se muestra en el siguiente ejemplo. ejempl o. Lo primer primero o que debe hacer es llamar al métod método o 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 por writeObject en el mismo orden en que se escribió. El método readObject tambi también én puede realiz realizar ar cálcu cálculos los o actu actualiza alizarr 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 inme in medi diat atas as.. Cua uallqu quie ierr se seri rial aliz izac aciión re requ quer erid ida a po porr la sup uper ercl clas ase e se man anej eja a automáticamente. Sin embargo, una clase que necesita coordinarse explícitamente con su superclase para serializarse puede hacerlo implementando el interface Externalizable Externalizable.. Implementar el Interface Externalizable
Para un co Para comp mple leto to co cont ntrol rol ex expl plíc ícit ito o de dell pr proc oces eso o de se seri rial aliz izac ació ión, n, un una a cl clas ase e de debe be implementar el interface Externalizable 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 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 Externalizable:: • •
•
•
Debe implementar el interface java.io.Externalizable interface java.io.Externalizable.. Debe Deb e imp implem lement entar ar un mét método odo writeExternal par para a sal salvar var el est estado ado del obj objeto eto.. También, Tambi én, debe coordinarse coordinarse explí explícitam citamente ente con sus superclase superclase para salvar su estado. Debe Deb e imp implem lement entar ar el mét método odo 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 es está tán n es esc cri ribi bien endo do fo form rmat atos os de defi fini nido dos s ex exte tern rnam amen ente te,, lo los s mé méto todo dos s 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 ten er cid cidado ado de pro proteg teger er la inf inform ormaci ación ón y las fun funcio ciones nes sen sensib sibles les.. Dur Durant ante e la des des-serializació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 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 Externalizable.. Alguna Algu nas s cl clas ases es po podr dría ían n en enco cont ntrar rar be bene nefi fici cios oso o pe perm rmit itir ir la es escr crit itur ura a y le lect ctur ura a pe pero ro 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. 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 compri com primid midos os par para a aho ahorra rrarr esp espaci acio. o. Los arc archiv hivos os Zip tam tambié bién n con contie tienen nen al fin final al 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. Saltam Sal tamos os al dir direct ectori orio o de ent entrad radas as y loc locali alizam zamos os la ent entrad rada a del fic ficher hero o 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 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 File 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 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 punte puntero ro de fich fichero ero. 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
Reescriba Reescr ibamos mos el eje ejempl mplo o de Escri Escribir bir Nues Nuestros tros Propi Propios os Strea Streams ms Filt Filtrados rados pa para ra qu que e funcione sobre RandomAccessFiles RandomAccessFiles.. Como RandomAccessFile implementa los interfaces DataInput y DataOutput DataOutput,, un be bene nefi fici cio o la late tera rall es qu que e lo los s st strea reams ms fi filt ltrad rados os ta tamb mbié ién n funcionan con otros streams DataInput y DataOutput incluidos algunos streams de acceso secuencial como DataInputStream y DataOutputStream DataOutputStream.. El ejemplo CheckedIOTest de Escribir Nuestros Propios Streams Filtrados implementa dos strea streams ms filt filtrados, rados, CheckedInputStream y CheckedOutputStream 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 CheckedOutputStream--calcula un checksum para los datos escritos en el stream-- pero opera sobre objetos DataOutput en lugar de sobre objetos OutputStream OutputStream.. De forma similar CheckedDataInput modifica CheckedInputStream para que ahora funciona funciona sobre objetos DataInput en lugar de objetos InputStream InputStream.. CheckedDataOutput contra CheckedOutputStream
Echemos un vistazo a las diferencias entre CheckedDataOutput y CheckedOutputStream CheckedOutputStream.. La primera diferencia es que CheckedDataOutput no desciende de FilterOutputStream FilterOutputStream.. En su lugar, implementa el interface DataOutput DataOutput.. public class CheckedDataOutput implements DataOutput Nota: Para mantener el ejemp ejemplo lo sencillo, la clase CheckedDataOutput realmente proporcionada en esta sección no está declarada para implementar DataOutput 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 de decl clara ara un una a va vari riab able le pri priva vada da pa para ra co cont nten ener er un ob obje jeto to DataOutput.. DataOutput private DataOutput out; Este es el objeto en el se escribirán los datos. El co cons nsttru ruc cto torr pa para ra CheckedDataOutput es diferente del constructor de CheckedOutputStream:: CheckedDataOutput se crea sobre un objeto DataOutput en vez CheckedOutputStream sobre un objeto OutputStream 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 CheckedOutputStream de la clase stream. Aquí están las únicas modificaciones echas a CheckedOutputStream para crear un filtro que funcione sobre objetos DataOutput DataOutput.. CheckedDataInput contra CheckedInputStream
CheckedDataInput requiere los mismos cambios que CheckedDataOuput CheckedDataOuput.. •
CheckedDataInput no desciende de FilterInputStream pero implementa el interface DataInput.. DataInput
Nota:
Para ma Para mant nten ener er el ej ejem empl plo o se senc ncilillo lo,, la cl clas ase e CheckedDataInput realmente proporcionada en esta sección no está declarada para implementar DataInput DataInput,, porque el interface DataInput especifica demasiados métodos. Sin embargo, la clase CheckedDataInput propor proporciona cionada da en el ejempl ejemplo o imple implementa menta 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 InputStream..
Además Adem ás de es esto tos s ca cam mbi bios os,, el mé méto todo do read ta tam mbi bién én se ha mo modi difi fic cad ado. o. El CheckedInputStream del ejemplo original implementa dos métodos read 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 Finalmente, principales para probar los nuevo nuevos s filt filtros. ros. CheckedDITest,, qu CheckedDITest que e ej ejec ecut uta a el fi filt ltro ro so sobr bre e fi fich cher eros os de ac acce ceso so se secu cuen enci cial al (o (obj bjet etos os DataInputStream y DataOutputStream DataOutputStream), ), y CheckedRAFTest CheckedRAFTest,, que ejecuta los filtros sobre ficheros de acceso aleatorio (objetos RandomAccessFiles 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 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 Además clases e int interf erface aces s exp explic licada adas s en est esta a lec lecció ción, n, java.io conti contiene ene 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 lueg lu ego o co cons nsul ulta tarr en el ob obje jeto to in info form rmac ació ión n so sobr bre e es ese e fi fich cher ero o (c (com omo o su pa path th 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 FilenameFilter accepta 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* foo*.. También podemos encontrar otros streams de entrada y salida en el paquete java.util.zip 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 DeflaterOutputStream y 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