Programación en castellano
-Tutoriales

Programacion en red


Entrada/Salida Parte 1

. Introducción

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 */
}
 
Patrocinados
 

Copyright © 1999-2007 Programación en castellano. Todos los derechos reservados.
Formulario de Contacto - Datos legales - Publicidad

Hospedaje web y servidores dedicados linux por Ferca Network

red internet: musica mp3 | logos y melodias | hospedaje web linux | registro de dominios | servidores dedicados
más internet: comprar | recursos gratis | posicionamiento en buscadores | tienda virtual | gifs animados