I/O de datos con streams

¿Qué son los streams?


Los streams son los objetos que se encargan de conectar el programa con las distintas fuentes o destinos de los datos que se procesan (teclado, monitor, discoduro, sockets...). Es uno de los aspectos a los que más cuesta acostumbrarse al principio, pero que una vez aprendido y comprendido da gran versatilidad al lenguaje.
En java todos los intercambios de datos se hacen mediante streams, que son implementados por la librería java.io, todos ellos descienden de la clase Object y según el tipo de datos que vayan a transportar descienden de alguna de las siguientes clases abstractas:

  • InputStream y OutputStream: de estas clases derivan todos los streams utilizados para transportar bytes, la utilización de una u otra de sus subclases va en función de cual sea el tipo de fuente o destino para los datos.
  • Reader y Writer: de estas clases derivan los streams especializados en el transporte de texto. Con métodos que permiten por ejemplo leer/escribir una línea entera. Al igual que sucede con los que manejan bytes la elección de una u otra subclase dependerá de cual sea el tipo de fuente o destino de los caracteres.
Posteriormente se trataran algunas clases derivadas de las anteriores, pero para conocer todas te invito a que visites los siguientes enlaces:
Clase abstracta Reader                      Clase abstracta Writer

¿Cómo se utilizan los streams?


La forma de utilizar los streams independientemente de cual sea la fuente o el destino de los datos es la siguiente:
  1. Crear el stream asociándolo con la fuente o el destino de los datos, según el caso.
  2. Leer o escribir los datos.
  3. Cerrar el stream.

Utilización de filtros con los streams.

Antes de poner ejemplos sobre la utilización de streams voy a explicar los filtros porque lo más normal es usar los streams asociados con estos.
Un filtro es un objeto que se asocia a un stream para aumentar su funcionalidad dotándolo de nuevos métodos para manipular los datos transportados. Una de las características más habituales de los filtros es que almacenan los datos que les van llegando desde el stream para que su manejo sea más cómodo y ágil.
Los filtros se usan de la siguiente forma:
  1. Se asocian con un estream.
  2. Se hacen las operaciones de lectura o escritura según sea necesario.
Algunos ejemplos de filtros son: BufferedInputStream, BufferedOutputStream, BufferedReader o BufferedWriter entre otros.

Ejemplos de utilización de streams.


1.- Lectura y escritura por consola.

Tal como cabe esperar por el titulo esta aplicación lee texto por consola y lo muestra nuevamente por la misma, tal como se muestra en la imagen.
Ejecución en consola del ejemplo.

Código de la solución:

package com.blogspot.misiniciosenjava.lecturaEscrituraConsola;
import java.io.*;
public class LecturaEscrituraConsola {
public static void main(String[] args) {
//Declaración e instanciación de Reader y Writer
InputStreamReader streamLector = new InputStreamReader(System.in);
OutputStreamWriter streamEscritor = new OutputStreamWriter(System.out);
//Declaración e instanciación de los filtros
BufferedReader buffLector = new BufferedReader(streamLector);
BufferedWriter buffEscritor = new BufferedWriter(streamEscritor);

String msg="Bienvenido escribe lo que quieras.\n";
String entrada="";
while(!entrada.matches(":salir:")){
try{
buffEscritor.write(msg);//Escribe msg en el stream de salida
buffEscritor.flush();//Envía el contenido del stream de salida a su destino
entrada=buffLector.readLine();//Lee el contenido del stream de entrada
msg="Has dicho: "+entrada+"\n";
}catch(IOException ex){
System.err.println(ex);
}
}
}
}
En este ejemplo se han creado primero los streams con sus referencias y luego los filtros con las suyas. Dado que una vez vinculados los filtros a los streams no son necesarias las referencias a los streams se puede crear y vincular el filtro junto con la instancia del stream en un solo paso tal como se indica a continuación para el BufferedReader:
BufferedReader buffLector = new BufferedReader(new InputStreamReader(System.in));
De esta forma el código resulta más limpio y elegante.


2.-Guardado de una página web en el disco.

En este ejemplo se hace una petición de una página web y se guarda en disco con el nombre seleccionado.

Código de la solución:

package com.blogspot.misiniciosenjava.ej2streams;
import java.net.*;
import java.io.*;
public class Ej2Streams {
static private void descargaWeb(String direccion,String nombre) throws MalformedURLException, IOException{
URL pagina;
if(direccion.contains("http://")){
pagina = new URL(direccion);
}else{
direccion="http://"+direccion;
pagina = new URL(direccion);
}
//Conexión con la pagina web
HttpURLConnection conexion = (HttpURLConnection)pagina.openConnection();
//Obtención de un IpnutStream a partir de la conexión y utilización de este para crear un reader
InputStreamReader streamConexion = new InputStreamReader((InputStream)conexion.getContent());
//Vinculación del filtro con el reader
BufferedReader lectorConexion = new BufferedReader(streamConexion);
//Creación del stream para escribir el archivo y vinculación con el filtro
BufferedWriter escritorArchivo = new BufferedWriter(new FileWriter(nombre));
String linea=null;
while((linea=lectorConexion.readLine())!=null){
escritorArchivo.write(linea); //Escritura de linea en el archivo destino
escritorArchivo.flush();
}
}
static public void main (String[] args){
BufferedReader leeConsola = new BufferedReader(new InputStreamReader(System.in));
String direccion=null;
String nombre=null;
try{
System.out.println("Introduce la dirección de la web");
direccion = leeConsola.readLine();
System.out.println("Introduce el nombre con el que se guardará");
nombre = leeConsola.readLine();
}catch(IOException ex0){
System.out.println("Error al leer del teclado: "+ex0);
}
try{
nombre=nombre+".html";
descargaWeb(direccion,nombre);
}catch(MalformedURLException ex1){
System.out.println("Formato de la dirección erróneo "+ex1);
}catch(IOException ex2){
System.out.println("Error al leer la web: "+ex2);
}
}
}
En este ejemplo se muestra como el mecanismo de utilización de los stream es el mismo aunque la fuente o destino de los datos sea distinto, cambiando únicamente el tipo de stream.

3.-Cifrado de un archivo.

Antes de que nadie me crucifique por el algoritmo de cifrado quiero indicar que se trata simplemente de una prueba de concepto para ilustrar la lectura y escritura de un archivo byte a byte, en ningún caso se pretende crear un sistema de cifrado-descifrado irrompible.
El algoritmo de cifrado consiste en sumar 50 al valor en el sistema decimal del byte, el algoritmo de descifrado es obvio.
Capturas de la ejecución del código del ejemplo 3

Código de la solución:


package com.blogspot.misiniciosenjava.ej3Streams;
import java.io.*;
/**
 * Aplicacion para cifrar/descifrar archivos
 * @author mil100
 *
 */
public class Cifrador {
/**
* Metodo para la lectura de datos de un BufferedReader
* @param eleccion es el BufferedReader del que leer los datos 
* @return null si falla la lectura y un String con la lectura en caso contrario
*/
private static String leeTeclado(BufferedReader eleccion){
try{
return eleccion.readLine();
}catch(IOException ex){
System.out.println("Error al leer del teclado: "+ex);
}
return null;
}
/**
* Muestra el menu y devuelve un int con la opcion elegida
* @param eleccion es el BufferedReader del que leer los datos
* @return -1 si falla un int con la opción en caso contrario.
*/
private static int menu(BufferedReader eleccion){
System.out.println("Elige la opción adecuada:");
System.out.println("\t(1)Cifrar archivo");
System.out.println("\t(2)Descifrar archivo");
System.out.println("\t(3)Salir");
String aux=leeTeclado(eleccion);
if(aux!=null)return Integer.parseInt(aux);
else return -1;
}
/**
* Se encarga de cifrar/descifrar un archivo.
* Para esto pide la ruta del archivo a cifrar/descifrar (origen) 
* y la del archivo cifrado/descifrado (destino)
* @param eleccion es el BufferedReader del que leer los datos
* @param clave es el int que se utiliza como clave para cifrar los datos
*/
private static void cifrado(BufferedReader eleccion,int clave){
System.out.println("Introduce la ruta y nombre del archivo a "+(clave>=0? "cifrar":"descifrar"));
String archivoFuente=leeTeclado(eleccion);
System.out.println("Introduce la ruta y nombre para el archivo "+(clave>=0? "cifrado":"descifrado"));
String archivoDestino=leeTeclado(eleccion);
if(archivoFuente==null || archivoDestino==null){
System.out.println("Tienes que introducir ruta y nombre para ambos archivos");
return;
}
//Se crean las referencias BufferedInputStream y BufferedOutputStream
BufferedInputStream fuente=null;
BufferedOutputStream destino=null;
try{
/*Se instancian los stream de lectura y escritura de los archivos a la vez que
* se vinculan con los filtros adecuados
*/
fuente = new BufferedInputStream(new FileInputStream(archivoFuente));
destino= new BufferedOutputStream(new FileOutputStream(archivoDestino));
int dato;
while((dato=fuente.read())!=-1){//lee un byte de la fuente
destino.write(dato+clave);//escribe un byte en el stream del destino
destino.flush();//escribe el contenido del stream del destino en el archivo
}
}catch(IOException ex){
System.out.println("Error con el manejo de archivos: "+ex);
}
}
public static void main(String[] args) {
BufferedReader eleccion= new BufferedReader(new InputStreamReader(System.in));
int opcion=0;
while(opcion!=3){
opcion=menu(eleccion);
switch(opcion){
case 1:
cifrado(eleccion,50);
break;
case 2:
cifrado(eleccion,-50);
break;
case 3:
break;
default:
System.out.println("La opción elegida no es válida");
}
}
}
}



No hay comentarios:

Publicar un comentario