Programacion en red

Este cap�tulo del Tutorial est� destinado a presentar las herramientas que ofrece Java para trabajar con datos externos, principalmente provenientes de archivos en disco, otros procesos en ejecuci�n, o (como veremos en detalle en el pr�ximo cap�tulo) de recursos de red.

Es fundamental que el lector ya se encuentre familiarizado con los aspectos b�sicos de Java y de la Programaci�n Orientada a Objetos, ya que en este cap�tulo se hace un uso extensivo de dichos conceptos, aunque no nos detengamos a comentarlos. Deben ser ya conocidos los temas relacionados con la sintaxis b�sica, la declaraci�n de clases, la creaci�n y manejo de objetos y la gesti�n de excepciones.

Comenzaremos con la presentaci�n del concepto de flujo ("stream" en ingl�s), muy com�n en otros lenguajes de programaci�n, y que sirve para englobar todos los sistemas de entrada/salida, independientemente del origen de los datos.

A continuaci�n seguiremos con aplicaciones b�sicas de los flujos, como el acceso a archivos.

Por �ltimo terminaremos presentando toda una serie de clases derivadas de los flujos b�sicos, �tiles para filtrar los datos, cambiarlos de formato, etc.

Nota: Aunque ciertos conceptos (como la clase java.io.PrintStream) no sean introducidos hasta el final de este cap�tulo, nos ser� necesario utilizarlos a lo largo de la explicaci�n cada vez que queramos escribir algo en la pantalla. Supondremos por ahora que cada vez que hagamos una referencia a construcciones del tipo System.out.println("Cadena de texto") o System.err.println("Mensaje de error"), simplemente estamos escribiendo texto en la pantalla, sin preocuparnos del hecho de que en realidad estemos trabajando con objetos de entrada/salida relativamente complejos.

.�El concepto de flujo

Podemos imaginar un flujo como un tubo donde podemos leer o escribir bytes. No nos importa lo que pueda haber en el otro extremo del tubo: puede ser un teclado, un monitor, un archivo, un proceso, una conexi�n TCP/IP o un objeto Java.

Todos los flujos que aparecen en Java (englobados generalmente en el paquete java.io) pertenecen a dos clases abstractas comunes: java.io.InputStream para los flujos de entrada (aquellos de los que podemos leer) y java.io.OutputStream para los flujos de salida (aquellos en los que podemos escribir).

Estos flujos, como hemos dicho antes, pueden tener or�genes diversos (un archivo, un socket TCP, etc.), pero una vez que tenemos una referencia a ellos, podemos trabajar siempre de la misma forma: leyendo datos mediante los m�todos de la familia read() o escribiendo datos con los m�todos write().

.�Trabajo con flujos

Vamos a ver en detalle las herramientas que nos proporcionan los flujos b�sicos, junto con algunos ejemplos. Supondremos que los objetos de tipo InputStream y OutputStream nos vienen dados, sin preocuparnos de c�mo han sido creados.

De hecho, en los ejemplos que aparezcan a continuaci�n nunca concretaremos la forma en que esos objetos son creados, dej�ndolo indicado mediante puntos suspensivos. Para fijar ideas, podemos imaginar que, cuando tengamos un InputStream en realidad estamos leyendo de la entrada standard (el teclado, generalmente), y cuando escribamos en un OutputStream estamos usando la salida standard.

.�Lectura de bytes individuales

Mediante c�digo como:

InputStream is = ...;
int b = is.read();

podemos obtener el siguiente byte del InputStream. Es importante darse cuenta de que el byte (8 bits) se devuelve como un dato de tipo int (32 bits), con un valor entre 0 y 255. En caso de que se haya alcanzado el final del archivo, read() devuelve un valor de -1.

.�Lectura de varios bytes

Primero creamos una matriz de bytes del tama�o adecuado. El tama�o de esta matriz es lo que le indica al m�todo read() cu�ntos bytes debe leer como m�ximo. Veamos el c�digo para leer 1024 bytes de un flujo de entrada:

byte[] miArray = new byte[1024];
InputStream is = ...;
int leidos = is.read(miArray);

La variable le�dos almacena el n�mero de bytes que se han le�do en realidad. Si se ha alcanzado el fin de archivo, devuelve el valor -1. Es importante ver que no hay ninguna garant�a de que vayamos a leer exactamente el n�mero de bytes especificado. El n�mero de bytes le�dos puede ser menor por varias razones: porque estamos leyendo de un archivo que se ha acabado, porque los datos de una conexi�n de red tardan en llegar o por cualquier otra raz�n. De cualquier forma, mientras el m�todo read() devuelva un valor distinto de -1, podemos seguir leyendo mediante sucesivas llamadas a read().

Otra variante de este m�todo es la siguiente:

byte[] miArray = new byte[2048];
InputStream is = ...;
int origen = 512;
int longitud = 1024;
int leidos = is.read(miArray, origen, longitud);

En este caso especificamos el n�mero de bytes a leer mediante la variable longitud, y la posici�n dentro del array donde se deben almacenar los datos la indica la variable origen. El c�digo anterior lee hasta 1024 bytes del flujo de entrada y los coloca en los elementos 512, 513, 514, etc. de la matriz miArray.

.��Cu�ntos bytes hay disponibles?

Los m�todos anteriores bloquean la ejecuci�n hasta que existen nuevos datos para ser le�dos. Si estos datos vienen, por ejemplo, desde una conexi�n de red, pueden pasar varios segundos (o m�s tiempo, dependiendo de la situaci�n) hasta que lleguen nuevos datos. Si estamos interesados en leer desde un InputStream pero no queremos arriesgarnos a que nuestro programa se pare durante un tiempo indefinido, podemos usar el m�todo available(), que devuelve el n�mero de bytes disponibles que podemos leer sin bloquearnos. Estos bytes disponibles ser�n los que el sistema operativo tenga almacenados en sus buffers de entrada.

Si, por ejemplo, tenemos varias conexiones en red abiertas, podemos usar el m�todo available() para determinar a cu�l de ellas debemos dedicar nuestra atenci�n. Por ejemplo, el siguiente c�digo recorre c�clicamente una matriz de referencias InputStream y lee los datos a medida que le van llegando:

InputStream[] is = new InputStream[10];
...
int n=0;
for(;;){
if (is[n].available()>0){
/* Leemos los datos y los procesamos */
}
n=(++n)%10;
}

.�Cerrar el flujo de entrada

Cuando ya no necesitamos leer datos de un flujo de entrada, debemos liberar los recursos asociados mediante el m�todo close(). Si no cerramos el InputStream expl�citamente, el flujo asociado se cierra cuando se destruye el objeto.

.�Escritura de bytes individuales

La clase OutputStream dispone de varios m�todos write(). Veamos un ejemplo en el que escribimos un solo byte en un flujo de salida:

OutputStream os = ... ;
int dato = 123;
os.write(dato);

C�mo en el m�todo read() de la clase InputStream, el m�todo write() recibe un byte dentro de una variable de tipo int (32 bits).

.�Escritura de varios bytes

El c�digo siguiente apenas merece explicaci�n:

byte[] matriz= { 65,66,67,68,69};
OutputStream os = ... ;
os.write(matriz); /* Escribe bytes 65,66,67,68,69 */
os.write(matriz,1,3); /* Escribe bytes 66,67,68 */

.�Cerrar el flujo de salida

Los recursos asignados al OutputStream se liberan con el m�todo close(). Este m�todo, adem�s, garantiza que los datos que hayamos escrito en el flujo pero que aun no hayan sido enviados (a un archivo, por ejemplo) se manden a su destino. En los casos en que queramos asegurarnos de que los datos han sido enviados, pero no queramos cerrar el flujo, podemos usar el m�todo flush(), que vac�a los buffers de salida.

.�Gesti�n de excepciones de entrada/salida

Los ejemplos que hemos visto hasta ahora han sido "simplificados" para resaltar los elementos m�s importantes. En una situaci�n real, es necesario tener algo m�s de precauci�n a la hora de trabajar con flujos.

Todos los m�todos read(), write(), available(), etc. que hemos visto antes lanzan excepciones del tipo java.io.IOException. Estas excepciones es obligatorio capturarlas, o aparecer�n errores en tiempo de compilaci�n.

En general, la partes de nuestros programas que trabajen con flujos deben estar dentro de una cl�usula try ... catch. Por ejemplo:

try{
InputStream is = ... ;
while(...){
/* Leemos y procesamos los datos */
}
} catch (IOException ioe){
System.err.println("Error al abrir el flujo tal y tal...");
ioe.printStackTrace();
}

En programas peque�os en los que no queramos complicarnos con estructuras de este tipo, podemos tomar el camino f�cil de mandar la excepci�n "hacia arriba". Por ejemplo:

public static void main(String args[]) throws IOException {
/* Programa creado por un programador vago */
}

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP
ARTÍCULO ANTERIOR

SIGUIENTE ARTÍCULO