Curso de Java. Flujos de datos. E/S de archivos.

los flujos de datos en java confieren a los programas la capacidad de poder interactuar con otros dispositivos de E/S o la misma memoria. Por ello los flujos de datos en java son una de las principales herramientas o características más importantes que debemos aprender del lenguaje Java.

Hasta ahora en el curso de Java hemos utilizado en la mayoría de ejemplos la clase EntDatos una clase implementada expresamente para la entrada de datos por teclado y que usa flujos de datos. Ya dijimos que para el programador novel en java, entender el funcionamiento de dicha clase iba a resultar complicado. Se requiere haber aprendido y asimilado antes muchas otras características del lenguaje.

llegado a este punto, si se ha seguido en su mayoría las lecciones de este curso, ya estamos preparados y con cierta formación en java, para entender lo que se va a explicar en esta página. Una vez leída la misma, sabremos ¿que es un flujo de datos?, ¿Que tipos de flujos de datos hay? y ¿Como podemos trabajar con los flujos de datos en nuestros programas java?. Además comprender lo que hace la clase EntDatos será tarea fácil.

¿Que són los flujos de datos o streams en java?

Un flujo de datos en java es lo que permite que un programa realizado en Java u otro lenguaje de programación pueda leer o escribir datos en otro periférico, programa, etc. Por ejemplo podemos leer o escribir datos usando flujos de datos en archivos del disco duro, escribir usando el teclado (Tal como hacemos con la clase EntDatos), leer o escribir en un DVD, realizar impresiones, etc.

¿Que tipos de flujos de datos principales tiene java?

Según el tipo de datos, tenemos los siguientes flujos de datos:

Realmente, nos bastaría con usar Flujos de bytes para realizar cualquier intercambio de datos, pero ya veremos que dependiendo del tipo de dato resulta mas práctico usar uno u otro tipo de flujo de dato.

 

Flujos de bytes o Binarios.

Cuando estamos usando flujos de bytes estamos manejando el tipo de dato mas pequeño que hay en representación de caracteres, el Byte (8 bits). las clases que usamos en flujos de bytes descienden de las clases Abstractas InputStream y OutputStream.

En el ejemplo siguiente mostramos una manera de dar uso a los flujos de Bytes. Para ello usamos dos clases nuevas FileInputStream y FileOutputStream que heredan de las anteriores clases abstractas nombradas:

Ejemplo de lectura y escritura en archivos usando flujos de datos binarios.

¿Que hace este programa de flujos binarios?

  1. Importa las clases FileInputStream y FileOutputStream del paquete java.io y que necesitaremos para los flujos de E/S.
  2. Crea los objetos de cada tipo de flujo de dato (De Entrada o lectura y de Salida o escritura) y pasa a cada constructor el archivo correspondiente. Recordemos que los archivos deben de estar alojados en el mismo proyecto o carpeta del proyecto, de no ser así hay que indicar la ruta donde se encuentran.
  3. Leemos el primer byte del archivo original con el método abstracto heredado read() y lo asignamos a la variable int «char1».
  4. Creamos un bucle que recorrerá el archivo original en su totalidad y copiara carácter a carácter en el archivo copia usando el método abstracto heredado write(). El final del bucle se controla cuando se llega al final del archivo original y se «retorna -1«.
  5. Una vez usado los flujos de datos siempre hay que cerrarlos usando el método close().
  6. Siempre que se trabaja con lectura y escritura de archivos y flujos de E/S pueden producirse excepciones. De momento en el curso de Java no hemos tocado el tema de las excepciones, por eso solo se ha añadido «throws FileNotFoundException, IOException» en la cabecera de la clase de este ejemplo. Este añadido permite que el compilador de java deje ejecutar el programa.

Ejecución del programa.

Trás ejecutar el programa podemos comprobar como se ha leido el fichero «original.txt» y se ha copiado todo su contenido en el fichero «copia.txt» inicialmente vacio:

Curso de Java. Flujo de Bytes. lfichero original.

Curso de Java. Flujo de Bytes. Fichero copia.

Debug del programa con punto de interrupción.

Para comprobar como la lectura y la copia se realiza Byte a Byte o carácter a carácter colocamos un punto de interrupción en la ejecución del programa y borramos el contenido del fichero «copia.txt»:

Curso de Java. Flujos Binarios. Punto de interrupción.

 

Al hacer debug la ejecución del programa se detiene en dicho punto. La salida output de netbeans muestra el valor de la variable «char1»:

Curso de Java. Flujos de Bytes. Debug proyecto.

Comprobamos que dicha variable contiene el valor entero ‘69‘ que se corresponde con el carácter E en ASCII. Es decir a leído y copiado el primer carácter del archivo «original.txt». Si ahora volvemos a abrir el archivo «copia.txt» verificaremos que solo se ha copiado un carácter:

Curso de Java. Flujos de Bytes. Debug. Fichero copia.txt

Utilizar un Buffer en flujos de datos Binarios.

Si bien es cierto que con flujos de datos binarios podemos realizar cualquier lectura o escritura de datos, no es un tipo de flujo de dato que resulte muy eficaz. El hecho de realizar una lectura o escritura byte a byte, supone un exceso de solicitudes al sistema operativo para la E/S de archivos o dispositivos y un consiguiente gasto de recursos. Este problema se puede suavizar un poco usando un buffer tanto para los flujos de datos de Entrada como para los flujos de datos de salida.

Para usar Bufer en flujos de datos binarios podemos importar las clases BufferedInputStreamBufferedOutputStream del paquete java.io. Su utilización es muy sencilla como muestra el siguiente ejemplo:

Ejemplo de flujo de datos binarios usando un buffer para Entrada y Salida.

Al mirar este ejemplo vemos que se han importado las clases BufferedInputStreamBufferedOutputStream. Además se han sustituido las dos variables de las clases FileinputStream y FileOutputStream por las nuevas de tipo buffer importadas. Y por último podemos apreciar también como dichos buffer sirven de envoltorio a los anteriores flujos de datos binarios que ahora se pasan como parámetros a los constructores de los buffer.

Ejecución del programa.

En principio al ejecutar el programa no notamos diferencia alguna con la ejecución del programa anterior (sin buffer), a la vista muestra mismo resultados en el archivo copia.txt:

Curso de Java. Flujo de Bytes. lfichero original.

Curso de Java. Flujo de Bytes. Fichero copia.

La diferencia la apreciaremos si observamos con detalle como se realiza dicha copia, como vemos tras crear un nuevo punto de interrupción y hacer debug en el proyecto:

Debug del programa con dos puntos de interrupción.

En esta ocasión para ver como funciona cada uno de los objetos buffer crearemos dos puntos de interrupción:

Curso de Java. Flujos de Bytes. Ejemplo con buffer.

Al hacer debug en el proyecto, el curso del programa realiza una parada en el primer punto de interrupción. Cuando vamos dando avance paso a paso (no interno), comprobamos que el buffer de lectura se mantiene siempre con valores nulos. Pero esto tan solo es un espejismo, porque el buffer ya esta trabajando y cuando llega el curso del programa a la sentencia char1 = leer.read(); automáticamente aparecen todos los valores en el buffer, del contenido leido del archivo original.txt.

En el bufer de salida o escritura en el fichero copia.txt se aprecia mejor el trabajo que realiza el buffer.  Si miramos los valores del buffer vemos que este empieza a llenarse según va avanzando la ejecución del programa. Y la variable entera char1 va almacenando el último carácter que se le va asignando:

Curso de Java. Flujos de Bytes con Bufer. Debug con dos puntos de interrupción.

En esta imagen se aprecia como el buffer ya ha almacenado en este paso de ejecución del programa los valores {69, 110, 32, 117} equivalente a los caracteres «En u». Y en la siguiente imagen vemos que la variable char1 va almacenando el último valor asignado:

Curso de Java. Flujos de Bytes con Bufer. Debug con dos puntos de interrupción.

Sin embargo, si abrimos el fichero copia.txt podemos comprobar como no se ha accedido a él para escribir ningún dato, manteniéndose de momento vacio, con el consiguiente ahorro potencial de accesos al archivo y recursos solicitados:

Curso de Java. Flujos de Bytes con Bufer. Archivo copia.txt vacio

Flujos de Entrada y Salida de caracteres.

Aunque los flujos de datos binarios son la base de todos los flujos, no son demasiado prácticos en el uso con ficheros de texto, en este sentido es mejor usar flujos de caracteres.

En occidente principalmente suele usarse ASCII para valores de caracteres. De cara a la presentación local de caracteres, no supone realmente gran problema adaptar un programa que use flujo de datos tipo flujo de caracteres.

Los Flujos de entrada heredan de la clase ImputStreamReader y los Flujos de Salida heredan de la clase OutputStreamWriter. Respectivamente cada flujo de datos puede usar los constructores de las clases FileReader y FileWriter especialmente diseñadas para manejarse con archivos.

En el siguiente ejemplo interactuamos con tres archivos de texto (txt). Del  «archivo1» solo leemos (usando FileReader), en el segundo archivo «archivo2″escribimos la información leida de «archivo1» (usando BufferedWriter) y en el tercer archivo «archivo3″escribimos la información leida de «archivo1» (Usando FileWriter):

Para la realización de este ejemplo hemos considerado que los tres archivos se encuentran incluidos en el mismo proyecto:

Curso de java. trabajando con flujos de datos.ruta archivos

De encontrarse en otras ubicaciones se tendría que indicar las rutas exactas a los mismos. Una vez ejecutado este programa tendríamos las siguientes salidas en archivos y consola:

Archivo1

Curso de java. trabajando con flujos de datos. Archivo1

Archivo2

Curso de java. trabajando con flujos de datos. Archivo2

Archivo3

Curso de java. trabajando con flujos de datos.archivo3

Salida por consola.

Curso de java. trabajando con flujos de datos. Salida consola.

¿Que hace este programa?

  • Importa las clases FileReaderFileWriter del paquete java.io y que nos permiten usar el método readLine() para leer lineas de texto completas.
  • También importamos las clases BufferedReaderBufferedWriter del paquete java.io, que nos permiten usar los buffer  y la clase PrintWriter que nos permite presentar el texto con formato.
  • Creamos los objetos de cada flujo de datos.
  • Usamos un bucle while para recorrer todo el archivo hasta que el método readline devuelva null que indicará que se ha llegado al final del archivo leido.
  • Otra cosa a destacar es que usando BufferedWriter no conservamos la estructura (Separador de lineas) del texto original.
  • Finalmente siempre debemos cerrar los flujos abiertos con el método close(). (Cuando veamos excepciones hablaremos de la forma correcta de emplear el método close en un bloque finally.)

Flujos estándar y Consola.

Flujos estandar.

Existen tres tipos de flujos estandar:

  1. System.in que és un flujo de bytes y sirve para entrada al sistema desde teclado.
  2. System.out que usa objetos de la clase PrintStream (flujos de Bytes) y se utiliza para salida en pantalla.
  3. System.err que también usa objetos de la clase PrintStream (flujos de Bytes) y esta enfocado a salida en pantalla o archivo, de los errores presentados.

Estos tres tipos de flujos se definen automáticamente y no es necesario abrirlos.

Para utilizar el flujo estandar System.in como una secuencia de caracteres, tenemos que pasarlo como parámetro al constructor de la claseInputStreamReader y crear un nuevo objeto de la misma:

Consola.

El paquete java.io dispone de la clase Console, creada como alternativa a los tres tipos de flujos estandar explicados anteriormente. Un objeto de esta clase Console, mejora y aporta nueva funcionalidad a los flujos de caracteres.

Para usar un objeto de la clase Console tenemos primero que asegurarnos de que el mismo está disponible. Para ello hay que llamarlo desde la propia clase System:

Si el método no está disponible retornará «NULL».
La consola es idónea para programas de tratamiento de datos protegidos con contraseña, ya que tiene método especialmente diseñados para tal fin como puede ser el método readPassword.

¿Te ha gustado? Compártelo.

Tu comentario es importante, ¡Animate a Comentar!

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

  Acepto la política de privacidad

Información sobre protección de datos

  • Responsable: MIGUEL ANGEL SANZ
  • Fin del tratamiento: Moderar los comentarios. Responder las consultas
  • Legitimación: Tu consentimiento
  • Destinatarios 1&1 Internet España S.L.U. Politica de privacidad. https://www.ionos.es/terms-gtc//terms-privacy/.
  • Derechos: Acceder, rectificar y suprimir los datos.
  • Información adicional: Más información en nuestra política de privacidad.