Entrada y salida de datos

 

 

El acceso a disco y el flujo de información en Java se controla utilizando las clases del paquete java.io. Hay dos grupos de clases que hay que aprender a utilizar: las correspondientes a archivos y directorios, relacionadas con la clase File y las correspondientes a corrientes de datos (streams), relacionadas con las clases abstractas InputStream y OutputStream. Las corrientes de datos se pueden generar de muchas formas, una de ellas es usando archivos, pero no es la única. También se crean corrientes de datos a partir de vectores de bytes, cadenas, direcciones de internet, sockets de comunicación, etc...

 

Archivos y directorios (File)

En Java se utiliza el concepto de archivos de Unix, es decir, tanto los archivos de datos como los directorios se representan como objetos de una misma clase, la clase File. En el fondo un objeto File no es más que una dirección de un disco. Puede representer un archivo, un directorio o simplemente una dirección donde por ejemplo se podría crear un archivo o un directorio. La clase File sirve para representar sólo los aspectos externos de los archivos, no sirve ni para leer ni para escribir en ellos. Esto puede apreciarse claramente echando un vistazo a los constructores, las variables y los métodos principales de la clase File:

Constructores de File
File(File parent, String child)
          Crea un fichero  de nombre child en el directorio representado por parent.
File(String pathname)
          Crea un fichero con la dirección pathname.
File(String parent, String child)
          Crea un fichero o directorio de nombre child en el directorio pathname.

Hay tres constructores. El tercero pide como parámetro una dirección que puede ser la de un directorio o la de un archivo. El segundo pide la dirección de un directorio y el nombre de un subdirectorio o un archivo. El primero pide un objeto de la clase File, que debe corresponder a un directorio, y el nombre de un subdirectorio o archivo. Para llamar estos constructores no es necesario que los archivos o directorios existan. 

Variables de File
static String pathSeparator
          El caracter que se usa en el sistema operativo como separador de direcciones, en forma de cadena. (En Windows es ; y en Unix es :   )
static char pathSeparatorChar
          El caracter que se usa en el sistema operativo como separador de direcciones, en forma de char. (En Windows es ; y en Unix es :  )
static String separator
          El caracter que se usa en el sistema operativo como separador de directorios en una dirección de archivo, en forma de una cadena. (En Windows es \ mientras que en Unix es / ).
static char separatorChar
          El caracter que se usa en el sistema operativo como separador de directorios en una dirección de archivo, en forma de una cadena. (En Windows es \ mientras que en Unix es / ).

Las constantes pathSeparatorChar y separatorChar son los caracteres que el sistema operativo usa como separador de directorios en la variable PATH  y como separador de directorios en los nombres de archivos. En DOS y Windows pathSeparatorChar=';' y separatorChar='\'. pathSeparator y separator son los mismos separadores pero en forma de cadenas en lugar de caracteres, en DOS y en Windows: pathSeparator=";" y separatorChar="\" . En Unix  pathSeparator=":" y separatorChar="/".

Algunos métodos de la clase File
 boolean canRead()
          Prueba si la aplicación actual puede leer el fichero.
 boolean canWrite()
         Prueba si la aplicación actual puede modicar el fichero.
 int compareTo(File pathname)
          Compara lexicográficamente la dirección de este File con pathname.
 boolean createNewFile()
          Crea un archivo vacío si el actual no existe.
 boolean delete()
          Elimina el archivo o directorio denotado por este File.
 boolean equals(Object obj)
          Prueba la igualdad de este File con el objeto obj.
 boolean exists()
          Pruebe si el fichero o directorio existe.
 String getAbsolutePath()
          Devuelve la dirección absoluta del fichero o directorio.
 String getCanonicalPath()
          Devuelve la dirección canónica del fichero o directorio.
 String getName()
          Devuelve el nombre del fichero o directorio.
 String getParent()
          Devuelve la dirección del directorio "padre" del actual fichero o directorio. Devuelve null si la dirección del fichero no tiene un padre explícito.
 String getPath()
          Devuelve la dirección de este fichero o directorio.
 boolean isAbsolute()
          Prueba si la dirección del fichero o directorio es absoluta.
 boolean isDirectory()
         Prueba si el File es un directorio.
 boolean isFile()
          Prueba si el File es un directorio.
 boolean isHidden()
          Prueba si el archivo o directorio es "escondido".
 long lastModified()
          Devuelve el momento en que el archivo o directorio fue modificado por última vez.
 long length()
          Devuelve el tamaño (en bytes) del archivo.
 String[] list()
          Devuelve un vector con los nombres de todos los archivos y directorios que contiene este directorio.
 String[] list(FilenameFilter filter)
          Devuelve un vector con los nombres de todos los archivos y directorios que contiene este directorio y satisfacen el filtro filter.
static File[] listRoots()
          Devuelve las direcciones de todas las "raíces" del sistema, esto incluye todos los discos.
 boolean mkdir()
          Creat el directorio con este nombre siempre y cuando el directorio padre exista.
 boolean mkdirs()
          Crea el directorio con este nombre y todos los necesarios que aún no existan.
 boolean renameTo(File dest)
          Cambia el nombre del archivo o directorio al de dest.
 boolean setLastModified(long time)
          Cambia la fecha y hora del archivo a la determinada por time.
 boolean setReadOnly()
          Marca este archivo o directorio como de "sólo lectura".
 URL toURL()
          Convierte la dirección de este archivo o directorio en una dirección URL.

El método exists() nos informa de la existencia de un archivo y el método mkdirs() crea todos los directorios necesarios para que un objeto de la clase File corresponda a un directorio real en el disco. Los métodos isFile() e isDirectory() nos informan si el objeto de la clase File es un archivo o un directorio. getName() devuelve el nombre del archivo o directorio y getPath() devuelve la dirección relativa al directorio actual. getAbsolutePath() devuelve la dirección absoluta, es decir referida a la raíz del disco.  isAbsolute() nos informa si la dirección del archivo o directorio está dada (por construcción) en forma absoluta. El método delete()  borra el directorio o archivo del disco y el método renameTo(String File dest) cambia y/o mueve el archivo (o directorio) a la nueva dirección. canRead() y canWrite() nos informan si el archivo se puede leer y si se puede escribir en él respectivamente. Ambos métodos devuelven false si se trata de un directorio.El método list() devuelve los nombres de todos los archivos y subdirectorios contenidos en File si se trata de un directorio.

El primer ejemplo de esta unidad es un programa que recibe como parámetro la dirección de un archivo o directorio y devuelve sus datos obtenidos usando los métodos de la clase File.

Para llamarlo desde la línea de comandos hay que escribir:

java ejem07.datos <dirección1> <dirección2> ...

El alumno debe probar el programa pasándole direcciones de archivos y directorios. Observará que la primera línea siempre le devuelve la lista de todas las "raíces" disponibles en su ordenador. Luego escribe los datos del archivo. Pruebe a pasarle nombres de archivos que existen y otros que no existen, nombres de directorios, direcciones absolutas y relativas, pruebe a usar *,  pruebe a usar caracteres inválidos, etc... El alumno observará, por ejemplo, que el método que detecta caracteres inválidos es getCanonicalPath().

Este programa es una buena herramienta para conocer la clase File. Caben señalar dos detalles del programa que utilizan cosas que aún no se han explicado.

  1. La fecha y hora de la última modificación viene codificada en un número de tipo long que mide los milisegundos que han pasado desde el momento: January 1, 1970, 00:00:00 GMT. El alumno podrá observar que para convertir este valor a unos datos inteligibles se usaron las clases java.util.Date y java.text.SimpleDateFormat.
  2. El programa permite al usuario leer los datos de cada archivo y espera a que pulse <intro> para continuar con los datos del siguiente. Esto se hace convirtiendo la corriente de entrada System.in en un BufferedReader y utilizando el método readLine(). Esto se explica más adelante en esta misma unidad, en la sección de corrientes de texto.

Ahora estudiaremos los "filtros" que son una herramienta útil para obtener subconjuntos de archivos de un directorio.

El método list(FilenameFilter f) devuelve sólo los archivos o directorios que el filtro f acepta. FilternameFiter es una interfaz del paquete java.io con un solo método:

public boolean accept(File directorio,String name);

de manera que un FilternameFilter es cualquier clase que implemente esta interfaz.

El siguiente ejemplo muestra una implementación de un filtro que acepta sólo los archivos con una cierta extensión. La aplicación usa args[0] para el directorio y args[1] como la extensión para el filtro.

Para probar el ejemplo hay que hacer la siguiente llamada en la consola:

java ejem07.dir <directorio> <filtro>

Una observación importante que hay que hacer es la ausencia de métodos de lectura o escritura en la clase File. Para leer el contenido de un archivo o escribir en él es necesario utilizar otras clases. Hay dos opciones. La primera es utilizar la clase RandomAccessFile que proporciona todos los métodos necesarios para leer y escribir en archivos. La otra opción es utilizar corrientes de datos o Streams. La clase RandomAccessFile proporciona una alternativa de entrada y salida cómoda y completamente independiente de los Streams. Pero es importante señalar que la alternativa de los Streams es más general y muy útil y la única posible para internet, por lo que se le dedica el resto del capítulo.

 

Corrientes de entrada y salida (Streams)

El paquete java.io tiene varias clases que son derivadas de dos clases básicas: InputStream y OutputStream y que representan corrientes de entrada de bytes provenientes de una fuente o de salida de bytes que tienen un destino definido, respectivamente. La fuente de una corriente de entrada o InputStream puede ser un archivo, el teclado, un array de bytes en la memoria RAM, una conexión a un URL en internet o muchas otras cosas. El destino de una corriente de salida o OutputStream puede ser un archivo, una impresora, un array de bytes, una conexión a un URL en internet o muchas otras cosas.

Éstos son los métodos de InputStream y OutputStream:

Métodos de InputStream
 int available()
          Devuelve el número de bytes que se pueden leer o saltar sin bloquear la corriente.
 void close()
          Cierra la corriente de entrada y libera los recursos del sistema que esté usando.
 void mark(int readlimit)
          Marca la posición actual de la corriente de entrada.
 boolean markSupported()
          Prueba si esta corriente de entrada soporta los métodos mark y reset.
abstract  int read()
          Lee el siguiente byte de la corriente de entrada.
 int read(byte[] b)
          Lee bytes de la corriente de entrada y los deposita en el vector b.
 int read(byte[] b, int off, int len)
          Lee hasta len bytes de la corriente de entrada y los deposita en b a partir de off.
 void reset()
          Coloca la corriente de entrada en la posición que tenía la última vez que se invocó mark.
 long skip(long n)
          Salta y descarta los siguientes n bytes de la corriente de entrada.

Métodos de Output Stream
 void close()
          Cierra la corriente de salida y libera los recursos del sistema que está utilizando.
 void flush()
          Fuerza a que se escriba inmediatamente todo lo que pueda estar en el buffer de salida.
 void write(byte[] b)
          Escribe todos los b.length bytes del vector b a la corriente de salida.
 void write(byte[] b, int off, int len)
          Escribe  len bytes del vector b comenzando en la posición off.
abstract  void write(int b)
          Escribe un byte especificado por el int b.

  Los métodos más importantes son los que sirven para leer y escribir: read y writeLos tres métodos read sirven para leer bytes.

  public abstract int read() throws IOException;
  public int read(byte[] b) throws IOException;
  public int read(byte[] b,int pos, int n) throws IOException;

El primero lee un solo byte y devuelve su valor como un entero entre 0 y 255. El segundo lee tantos bytes como pueda hasta llenar el vector (array) b que se le pasa como parámetro o hasta que se acabe la corriente. El último lee a lo más n bytes y los pone en el vector b, a partir de la posición pos. ¿Qué ocurre si la corriente de entrada se termina? En ese caso el resultado de de los métodos read es -1. Los tres métodos de escritura write son análogos a los de lectura.

  public abstract void write(int b) throws IOException;
  public void write(byte[] b) throws IOException;
  public void write(byte[] b,int pos, int n) throws IOException;

El primero escribe un byte aunque se le pase como parámetro un entero. El método convierte el entero a byte y luego lo escribe. Para no perder información al método write(int n) sólo se le deben pasar valores n entre 0 y 255. El segundo método write escribe todos los bytes del vector b que se le pasa como parámetro y el tercero escribe n bytes del vector b comenzando desde la posición pos.

Como primeros ejemplos de corrientes de entrada y salida veremos las que se obtienen de ficheros. Para estos ejemplos usaremos la clase File que se explica en la siguiente unidad. Por ahora basta saber que la clase File representa los ficheros o archivos, que para crear un objet File basta dar un nombre de archivo válido y que si el archivo creado existe entonces el método length() devuelve su tamaño (el número de bytes que contiene).  A partir de un objeto File se pueden crear corrientes de entrada y salida mediante los constructores FileInputStream(File f) y FileOutputStream(File f) respectivamente.

El primer ejemplo se llama creaFichero. Es un programa   que crea el archivo ejem07\fichero.dat y guarda en él los enteros 10,100,1000 y -10. El segundo se llama leeFichero y lo que hace es abrir el archivo  ejem07\fichero.dat y leer byte a byte toda su "longitud" escribiendo el resultado en la pantalla.

El alumno debe ejecutar ambos programas. Observará que el primer programa crea un archivo ejem07\fichero.dat que mide exactamente cuatro bytes. También observará que el resultado al aplicar leeFichero es

10
100
44
246

en lugar de

10
100
300
-10

y deberá explicar porqué se obtiene este resultado.

A continuación explicamos brevemente los otros métodos de las clases InputStream y OutputStream.

available() nos dice el número de bytes que se pueden leer sin bloqueo. Esto quiere decir que son los bytes que se pueden leer sin tener que esperar por más, que están ya disponibles. Este método no debe emplearse para intentar averiguar si una cooriente de datos ya se agotó pues para muchas corrientes de entrada el resultado suele ser siempre cero y eso no quiere decir que ya se llegó al final, sino que habrá que esperar hasta que haya nuevos datos.

Por supuesto close() en ambas clases sirve para cerrar la corriente y liberar los recursos que puedan estarse usando. Por ejemplo, si una corriente C está escribiendo en un archivo A, no puede abrirse otra corriente para escribir en él mientras no se haya "cerrado" la corriente C.

Si markSupported() regresa true entonces se pueden utilizar los métodos mark(int límite) y reset(), el primero marca la posición de la lectura mientras no se lean más de límite bytes y reset() regresa la lectura a ese lugar, siempre y cuando no se haya excedido el límite de bytes leidos. skip(int n) hace que la lectura prosiga saltándose n bytes. flush() obliga a que se escriba físicamente lo que aún esté en el almacén temporal de escritura (por ejemplo cuando se está utilizando un BufferedOutputStream).

El siguiente ejemplo es una sencilla aplicación para copiar archivos basada en lo que se ha visto en esta unidad. El alumno puede probarla llamándola desde la línea de comandos con

java ejem07.copyFile file1 file2

 

Corrientes de texto

Java ofrece una variedad de classs útiles para leer y escribir texto. Para simplificar las cosas aquí recomendamos las dos que más suelen utilizarse por la comodidad que ofrecen. 

Para leer texto recomendamos usar BufferedReader porque tiene el método readLine() que lee líneas de texto de manera eficiente y segura realizando las conversiones de caracteres necesarias. Para convertir un InputStream en un BufferedReader es necesario hacerlo en dos pasos como se ilustra en la siguiente línea aplicada al InputStream System.in .

new BufferedReader(new InputStreamReader(System.in));

Para escribir texto recomendamos BufferedWriter que tiene los métodos write(String) y newLine() con los que se pueden escribir líneas de texto una tras otra cómodamente. Para obtener un BufferedReader a partir de un OutputStream, igual que en el caso de la lectura, hay que hacerlo en dos pasos: primero se convierte el OutputStream en un OutputStreamWriter y luego éste en un BufferedWriter. Por ejemplo, para convertir System.out en un BufferedWriter se usaría:

new BufferedWriter(new OutputStreamWriter(System.out));

El siguiente ejemplo sirve para capturar y escribir en un archivo todas las líneas de texto que escriba el usuario en la línea de comandos hasta que escriba una vacía, momento en el cual el archivo se cierra y se lee escribiendo en la consola su contenido.

Observe cómo en el programa anterior se utiliza dos veces un BufferedReader. La primera para leer las líneas escritas por el usuario y la segunda para leer las líneas de texto del archivo.

También puede usarse para escribir texto la clase PrintStream pues su método println(String s)  es particularmente útil y cómodo para agregar líneas de texto a una corriente de salida. Es fácil crear un PrintStream a partir de cualquier OutputSteam usando el constructor que acepta como parámetro precisamente un OutputStream:

public PrintStream(OutputStream os);

Este sistema de leer y escribir líneas de texto se utiliza mucho en las aplicaciones llamadas de "línea de comandos". Con este sistema es posible crear programas interactivos en los que la interacción con el usuario se hace a través de la línea de comandos con líneas de código como ésta:

System.out.print("escriba su nombre:"); br.readLine();

en las que se da un mensaje al usuario y se lee su respuesta. Luego, el programa puede interpretar o almacenar la respuesta y preparar otra pregunta. Así se pueden crear muchos programas interactivos, son los llamados programa de "línea de comandos" o también programas "negros" en referencia a que normalmente la consola es negra con letras gris claro.

Este mismo sistema se utiliza mucho en las comunicaciones por internet, como podrá comprobarse en el curso JAVA INTERMEDIO.

 

Corrientes de datos

Las clases InputStream y OutputStream sólo permiten leer y escribir datos "byte a byte". Hay dos interfaces DataInput y DataOutput que resultan útiles para leer o escribir datos más complejos que simples bytes. Estos son los métodos de DataInputStream y DataOutputStream adicionales a los de InputStream y OutputStream respectivamente:

Métodos de la Interface DataInput
 boolean readBoolean()
          Lee un byte y devuelve true si el byte es distinto de cero y false si es cero.
 byte readByte()
          Lee un byte y lo devuelve.
 char readChar()
          Lee un char y lo devuelve.
 double readDouble()
          Lee ocho bytes y devuelve el double representado por esos ocho bytes.
 float readFloat()
          Lee cuatro bytes y devuelve el float  representado por esos cuatro bytes.
 void readFully(byte[] b)
          Lee bytes y los deposita en el vector de bytes b. Lee tantos bytes como caben en b mientras la corriente de entrada no se agote.
 void readFully(byte[] b, int off, int len)
           Lee hasta len bytes y los deposita en el vector b a partir de la posición pos.
 int readInt()
          Lee cuatro bytes y devuelve el int  representado por esos cuatro bytes
 String readLine()
          Lee una línea de texto.
 long readLong()
          Lee ocho bytes y devuelve el long representado por esos ocho bytes.
 short readShort()
          Lee dos bytes y devuelve el short representado por esos dos bytes.
 int readUnsignedByte()
          Lee un byte y lo devuelve en forma de un int no negativo, es decir entre 0 y 255.
 int readUnsignedShort()
          Lee dos bytes y devuelve un int con valor entre 0 y 65535.
 String readUTF()
          Lee una cuerda codificada en formato UTF-8 modificado.
 int skipBytes(int n)
         Intenta saltarse n bytes. Devuelve el número de bytes que realmente logró saltar.

 

Métodos de la Interface DataOutput
 void write(byte[] b)
          Escribe a la corriente de salida todos los bytes del vector b.
 void write(byte[] b, int off, int len)
          Escribe len bytes del vector b a partir de la posición off.
 void write(int b)
          Escribe el byte (consistente en los ocho bits menos significativos de) b.
 void writeBoolean(boolean v)
          Escribe un valor booleano a la corriente de salida.
 void writeByte(int v)
          Escribe el byte (consistente en los ocho bits menos significativos de) v.
 void writeBytes(String s)
          Escribe la cuerda s a la corriente de salida.
 void writeChar(int v)
          Escribe el char formado por los dos primeros bytes de v.
 void writeChars(String s)
          Escribe los char que forman la cadena s, dos bytes por cada caracter.
 void writeDouble(double v)
          Escribe el double v como ocho bytes.
 void writeFloat(float v)
          Escribe el float v como cuatro bytes.
 void writeInt(int v)
          Escribe el  int v como cuatro bytes.
 void writeLong(long v)
          Escribe el  long v  como ocho bytes.
 void writeShort(int v)
          Escribe dos bytes que representan el short v.
 void writeUTF(String str)
          Escribe la cadena str en el formato UTF modificado de Java.

Para usar estas interfaces basta crear subclases DataInputStream y DataOutputStream de cualquier InputStream o  OutputStream respectivamente usando los constructores:

public DataInputStream(InputStream is);
public DataOutputStream(OutputStream os);

El siguiente programa utiliza DataOutput para escribir varios datos en un archivo y luego lee los resultados (utilizando DataInput) y los escribe a la corriente de salida.

Para poder leer un archivo como el creado por el programa anterior, es necesario conocer su estructura. Note que la lectura de datos se hizo sabiendo qué tipo de dato seguía en cada paso. Para leer las dos líneas de la cadena escrita al final hubo que obtener un BufferedReader y utilizar dos veces su método readLine().

La lectura y escritura que proporcionan las interfaces DataInput y DataOutput v

 

Archivos de acceso aleatorio (RandomAccessFile)

La clase RandomAccessFile proporciona todos los métodos necesarios para leer y escribir en archivos de manera no necesariamente secuencial, es decir, en forma aleatoria. Contra lo que se podría suponer, la clase RandomAccessFile  no es una extensión de File. Son clases complementarias. Como se señaló anteriormente, File representa directorios y archivos, pero no proporciona métodos para acceder al interior de los archivos. En cambio RandomAccessFile sólo representa archivos y proporciona precisamente métodos para leer su contenido y escribir en ellos diferentes tipos de datos y en cualquier sitio. Esta clase es útil para aplicaciones que leen y escriben datos en archivos de un disco local, pero para acceder a datos en internet es necesario utilizar Corrientes o Streams. Éstos son los constructores de RandomAccessFile:

public RandomAccessFile(String nombre,String modo) throws IOException
public RandomAccessFile(File f,String modo) throws IOException

El segundo parámetro en ambos casos debe ser "r" o "rw" que significan que el archivo es de sólo lectura o de lectura y escritura respectivamente. El primer parámetro debe ser la dirección completa de un archivo o bien un objeto de tipo File que deberá ser un archivo y no un directorio. Llamar a uno de estos constructores abre el archivo si ya existe o lo crea si aún no existe, siempre y cuando la dirección sí exista. Todos los métodos de RandomAccessFile arrojan IOException, por lo cual todo lo que se programe usando esta clase debe escribirse dentro de una construcción:

try { ... } catch (IOException ioe) {}

RandomAccessFile tiene unos métodos para saber el tamaño del archivo en bytes y poder cerrarlo cuando se ha terminado de trabajar con él:

public long length() throws IOException
public void close() throws IOException

y otros que permiten saber la localización del puntero del archivo y colocarlo en una posición arbitraria:

public long getFilePointer() throws IOException
public void seek(long pos) throws IOException

Para leer y escribir RandomAccessFile tiene todos los métodos de las interfaces DataInput y DataOutput que se utilizan también con Streams, lo cual resulta cómodo pues sólo hay que aprender a usar un conjunto de métodos.

Los métodos de lectura leen, a partir de la posición del puntero, el número de bytes que ocupa el dato que se está leyendo y avanzan el puntero esos mismos bytes. Los de escritura igual, escriben a partir de donde está el puntero los bytes necesarios y avanzan el puntero. Algunos de estos métodos requieren de una explicación. readUnsignedByte y readUnsignedShort están diseñados para leer datos que fueron escritos por programas hechos en C. El resultado de la lectura es un entero pero el avance sólo es de uno y dos bytes respectivamente. Los métodos readFully leen todo lo que pueden hasta que se ha llenado la matriz de bytes (o la zona de n bytes que comienza en pos) o hasta que se termina el archivo, lo primero que ocurra. skipBytes(int n) sólo avanza el puntero saltándose n bytes. Los métodos de escritura pueden hacer crecer el archivo.  readLine() lee todos los bytes que encuentra antes de un paso de línea y los devuelve en una cadena. Es un método útil para leer archivos ascii. Los métodos writeBytes(String s) y writeChars(String s) ambos escriben una cadena pero el primero escribe un byte por caracter y el segundo dos bytes por caracter respetando el standard UNICODE. readUTF y writeUTF leen y escriben cadenas UNICODE en un formato llamado UTF-8 que se usa frecuentemente para transmitir datos y es compatible con ascii.

 

Otras corrientes de entrada y salida

Se pueden crear corrientes de entrada y salida a partir de diversas fuentes y con diversos destinos. Por ejemplo, como ya hemos visto en los ejemplos de esta unidad, se pueden crear InputStreams y OutputStreams a partir de archivos usando los constructores de la clase FilInputStream y FileOutputStream respectivamente:

public FileInputStream(File fuente);
public FileOutputStream(File destino);

También se puede crear un InputStream usando una matriz de bytes como fuente. Para ello se utiliza uno de los constructores de la clase ByteArrayInputStream:

public ByteArrayInputStream(byte[] b);
public ByteArrayInputStream(byte[] b,int pos,int n);

Si se desea tener un array de bytes como destino de un OutputStream, se puede crear usando los constructores de ByteArrayOutputStream:

public ByteArrayOutputStream();
public ByteArrayOutputStream(int n);

Estos constructores proporcionan un OutputStream al que se puede enviar toda la información que uno desee. El resultado de la construcción es una matriz de bytes (en el segundo caso comienza con un tamaño n) que va creciendo a medida que se necesite y que alberga toda la información que se le ha enviado. La matriz resultante se puede obtener usando el método:

public synchronized byte[] toByteArray();

de la misma clase ByteArrayOutputStream y que da solamente los bytes que se han escrito. El ejemplo pipeApplet de la siguiente sección usa un ByteArrayOutputStream.

También hay constructores para estas clases partiendo de las direcciones de los archivos en forma de cadenas o partiendo de su FileDescriptor.

En las comunicaciones por internet se utilizan InputStreams que se obtienen a partir de direcciones de internet o URL's. También se pueden crear Input y Output Streams usando sockets de comunicaciones.  Esto se estudia en el curso JAVA INTERMEDIO.

 

Otras subclases de InputStream y OutputStream

En esta sección describimos superficialmente algunas extensiones de InputStream y OutputStream que pueden tener alguna utilidad:

FilterInputStream y FilterOutputStream son subclases de InputStream y OutputStream que sirven para filtrar o transformar los datos. Las clases sólo añaden los constructores:

public FilterInputStream(InputStream is);
public FilterOutputStream(OutputStream out);

Si no se hacen subclases sobreescribiendo los constructores, el resultado es el InputStream o el OutputStream originales. Es precisamente al sobreescribir los constructores que se deben definir los filtros. Por ejemplo se puede definir un filtro que codifique datos de manera que sólo se puedan entender si se descodifican, es decir, si se pasan por el filtro inverso.

Casi todas las otras subclases se construyen como subclases de FilterInputStream y FilterOutputStream para aprovechar los posibles filtros que se deseen utilizar, aunque en la mayoría de las aplicaciones no se usa ningún filtro.

PrintStream es una subclase de FilterOutputStream que implementa una serie de métodos llamados print y println que sirven para escibir cómodamente datos mezclando cadenas con números. Estos métodos convierten todo a cadenas. El alumno ya ha utilizado muchas veces un PrintStream que es System.out, por lo que ya está familiarizado con su funcionamiento. Actualmente la clase PrintStream sólo se usa en System.out, para escribir texto en impresoras o archivos se deben usar siempre Writers: PrintWriter o FileWriter respectivamente.

BufferedInputStream y BufferedOutputStream son subclases que se obtienen usando los constructores

public BufferedInputStream(InputStream is);
public BufferedInputStream(InputStream is,int tamaño);
public BufferedOutputStream(OutputStream is);
public BufferedOutputStream(OutputStream is,int tamaño);

Lo que hacen es usar un "buffer" o almacén temporal con el objeto de mejorar la eficiencia de la lectura o la escritura. En el caso de la escritura es importante llamar al método flush() antes de cerrar la corriente. En los constructores tamaño especifica el número de bytes que se usarán en el buffer.

LineNumberInputStream y PushbackInputStream son otras dos extensiones de FilterInputStream. La primera tiene sólo dos métodos más: int getLineNumber() y void setLineNumber(int L) que permiten llevar control del número de líneas en corrientes de texto. Por supuesto, getLineNumber() devuelve el número de línea en que se encuentra el puntero. setLineNumber(int L) define una nueva numeración de las líneas, lo cual se usa por ejemplo cuando al comenzar una nueva corriente se le quiere asignar el número de líneas de la corriente anterior para numerar las líneas como si provinieran de una sola corriente. PushbackInputStream tiene un solo método público extra: unread(char ch) cuya función es agregar artificialmente a la corriente un caracter extra pecisamente en el lugar donde se encuentra. Esto es útil para convertir archivos de texto de sistemas operativos diferentes donde los pasos de línea pueden estar dados por caracteres diferentes.

SequenceInputStream es una subclase de InputStream que permite concatenar varios InputStreams. Tiene dos constructores:

public SequenceInputStream(InputStream s1,InputStream s2);
public SequenceInputStream(Enumeration e);

El primero crea un InputStream que consiste en s1 seguido de s2, El segundo tiene como parámetro una enumeración que debe ser de InputStreams y el resultado es un InputStream que recorrerá en orden todos los que vienen especificados en la enumeración.