Programacion en red

Hasta ahora nos hemos dedicado simplemente a escribir bytes en un flujo de datos. Aunque con �nicamente estas t�cnicas podr�amos conseguir leer y escribir cualquier cosa en un archivo, esta forma de trabajar es relativamente pesada, ya que cada vez que quisi�ramos escribir, por ejemplo, un entero de 32 bits en un archivo, tendr�amos que dividir los 32 bits en cuatro paquetes de 8 bits cada uno, e ir transmiti�ndolos a trav�s de flujo de datos. Para leer ese dato, el proceso ser�a el inverso: leer cuatro bytes del flujo y combinarlos para obtener el n�mero de 32 bits.

Como veremos, el paquete java.io nos proporciona varias herramientas para facilitarnos este trabajo.

.�La clase DataOutputStream

La clase DataOutputStream, heredera indirecta de OutputStream, a�ade a �sta �ltima la posibilidad de escribir datos "complejos" en un flujo de salida. Cuando hablamos de datos "complejos", en realidad nos referimos a tipo de datos primitivos, pero no restringidos �nicamente a bytes y a matrices de bytes, como en el caso de OutputStream.

Mediante la clase DataOutputStream podemos escribir datos de tipo int, float, double, char, etc. Incluso podemos escribir algunos objetos, como datos de tipo String, en una gran cantidad de formatos.

La forma general de trabajar con objetos de tipo DataOutputStream ser� la siguiente: obtenemos un objeto OutputStream (cuyo origen puede ser cualquiera: un archivo, un socket, una matriz en memoria, la salida standard, etc) y lo "envolvemos" en un objeto DataOutputStream, de forma que podamos usar la interfaz que nos proporciona este �ltimo. Para crear este DataOutputStream, le pasamos como par�metro el OutputStream a su constructor.

Cada vez que escribamos un datos "complejo" en un objeto DataOutputStream, �ste lo traducir� a bytes individuales, y los escribir� en el OutputStream subyacente, sin que nosotros tengamos que preocuparnos de la forma en que lo hace.

Veamos algunos ejemplos:

OutputStream os = ...;  
 DataOutputStream dos = new DataOutputStream(os);  
   
 int a = 3;  
 float b = 3.56754;  
 double c = -456.876345;   
 dos.writeInt(a);        // Escribimos un entero en el stream (4 bytes)  
 dos.writeFloat(b);    // Escribimos un n� de precisi�n simple (4 bytes)  
 dos.writeDouble(c); // Escribimos un n� de precisi�n doble (8 bytes)  

 dos.close();  
 os.close();

Los nombres de los m�todos usados son bastante descriptivos por s� mismos. En la documentaci�n del JDK podemos ver que existen otros m�todos, uno para cada tipo de dato primitivo: writeBoolean(), writeByte(), writeChar(), writeLong() y writeShort().

Si seguimos investigando en el JDK, descubrimos algunos m�todos de los que a�n no hemos hablado: writeBytes(), writeChars() y writeUTF(). Estos tres m�todos reciben como par�metros un objeto de tipo String, y lo escriben en el OutputStream subyacente usando diferentes formatos.

writeBytes() descompone la cadena de texto en bytes individuales (obtiene su c�digo ASCII) y los escribe en el flujo de salida. Si la cadena consta de n letras, escribe n bytes, sin a�adir ning�n delimitador ni de principio ni de fin de cadena.

writeChars() descompone la cadena de texto en chars individuales (obtiene su c�digo Unicode) y los escribe en el flujo de salida. Si la cadena consta de n letras, escribe n chars, sin a�adir ning�n delimitador ni de principio ni de fin de cadena.

writeUTF() escribe la cadena en un formato conocido como UTF-8. Este formato incluye informaci�n sobre la longitud exacta de la cadena.

La conclusi�n importante que debemos extraer de estos tres �ltimos m�todos es que solo el �ltimo nos permite recuperar la cadena con facilidad. Los otros dos m�todos no incluyen informaci�n sobre la longitud de la cadena, por lo que si otro programa necesita leer los datos que nosotros hemos escrito, es necesario conocer "a priori" la longitud de la cadena. Si no, es imposible saber el n�mero de bytes o de chars que debemos leer.

Un ejemplo de utilizaci�n:

...  
 String cad1 = "Me voy a convertir en bytes";  
 String cad2 = "Me voy a convertir en chars";  
 String cad3 = "Me voy a convertir en formato UTF";  
   
 dos.writeBytes(cad1);  
 dos.writeChars(cad2);  
 dos.writeUTF(cad3);

.�La clase DataInputStream

Escribir datos formateados no vale de nada si luego no podemos leerlos c�modamente. Para esta funci�n disponemos de la clase DataInputStream.

La clase DataInputStream est� preparada para leer datos generados por un objeto DataOutputStream. La especificaci�n garantiza que cualquier archivo escrito por un DataOutputStream, sobre cualquier plataforma y sistema operativo, ser� legible correctamente por un DataInputStream, sin que nos tengamos que preocupar de si las m�quinas son "little-endian" o "big-endian".

Supongamos que estamos intentando leer los datos escritos por el programa de ejemplo anterior (donde escrib�amos un int, un float y un double en un flujo de salida):

InputStream is = ...;   
 DataInputStream dis = new DataInputStream(is);   

 int x;   
 float y;   
 double z;   
 x = dis.readInt();   
 y = dis.readFloat();   
 z = dis.readDouble();   

 dis.close();   
 is.close();

�Qu� pasa si queremos leer las cadenas de texto que hemos escrito?. Es inmediato leer la cadena escrita en formato UTF. En cambio, leer las otras dos cadenas nos va a costar m�s trabajo, ya que debemos leer los bytes o los chars individuales, juntarlos de forma adecuada y construir la cadena resultante.

En este caso podemos hacer trampa, ya que sabemos que las cadenas cad1 y cad2 tienen una longitud de 27 letras exactamente. En una situaci�n normal, puede que no tengamos esta informaci�n.

int tam = 27;           // Hacemos trampas...   

 InputStream is = ...;   
 DataInputStream dis = new DataInputStream(is);   

 byte miNuevoArray[] = new byte[tam];   
 dis.readFully(miNuevoArray); /* Este m�todo es nuevo */   
 String cadenaConvertida = new String(miNuevoArray,0);   
  
 // Ahora tenemos que leer un mont�n de chars (16 bits)   
 // que forman el siguiente String escrito.   
 // Hay que hacer un bucle para ir leyendo los chars uno a uno   
    
 char otroArrayMas[] = new char[tam];   
 for(int n=0;n<tam;n++)   
         otroArrayMas[n]=dis.readChar();   

 String otraCadenaConvertida = new String(otroArrayMas);   

 // Queda leer el String en formato UTF-8   
 // Basta con llamar a readUTF(), ya que la longitud   
 // del String est� indicada en el propio archivo.   

 String ultimaCadena = dis.readUTF();

En el ejemplo anterior hemos usado un m�todo nuevo, DataInputStream.readFully(byte[]), que es b�sicamente equivalente a InputStream.read(byte[]), con la �nica diferencia de que no retorna hasta que hayan sido le�dos exactamente el n�mero de bytes que caben en la matriz.

Es f�cil apreciar las ventajas de usar los m�todos writeUTF() y readUTF().

.�Entrada/Salida en memoria

Existen ocasiones en que nos puede interesar acceder a cierta informaci�n en memoria como si estuvi�ramos leyendo desde una flujo de datos. Por ejemplo, podemos tener un objeto capaz de leer un archivo MPEG (v�deo comprimido) y mostrarlo posteriormente en pantalla. Supongamos que ese objeto est� dise�ado para leer los datos desde un InputStream, pero nosotros queremos que lea los datos desde una array de bytes que tenemos en memoria, cuyo contenido hemos generado nosotros de alguna forma.

Una forma de resolver el problema ser�a escribir el array de bytes en un archivo, y hacer que nuestro objeto reproductor de MPEG leyera el contenido.

Otra forma, m�s elegante y m�s eficiente que la anterior, ser�a crear un flujo que extrajera sus datos directamente desde nuestro array en memoria. Cualquier objeto que leyera datos de este flujo, en realidad estar�a sacando los datos secuencialmente de nuestro array.

La clase que nos permite hacer esto es DataArrayInputStream. Este clase tiene una constructor al que se le pasa como par�metro la matriz de bytes de la que debe leer:

int tam = 20;   
 byte[] buffer = new byte[tam];   

 for(int n=0;n<tam;n++)   
         buffer[n] = n;   

 ByteArrayInputStream bais = new ByteArrayInputStream(buffer);   
 ...   

 int c;   
 while((c=bais.read())!=-1)      /* Mientras leamos algo */   
         System.out.println("Hemos le�do el valor " + c);   
 ... 

Existe otra clase de este tipo muy �til: ByteArrayOutputStream. Mediante esta clase podemos escribir datos en un flujo, sabiendo que estos datos se almacenan internamente en una matriz de bytes. Esta matriz crece din�micamente a medida que escribimos datos en ella.

Una vez escritos los datos, podemos acceder a la matriz de bytes mediante el m�todo toByteArray(), que nos devuelve una copia de la matriz original.

Veamos un ejemplo:

ByteArrayOutputStream baos = new ByteArrayOutpuStream();   
 boolean condicion = false;   

 while(!condicion){   
            
         int dato = ...;   
         baos.write(dato);   
         condicion = ...;   

 }   

 byte[] bufferSalida = baos.toByteArray();

Una vez ejecutado el c�digo anterior obtenemos una matriz de bytes con todos los datos que hemos ido introduciendo a lo largo del bucle.

COMPARTE ESTE ARTÍCULO

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

SIGUIENTE ARTÍCULO