Skip to content

6.1 Streams (flujos)

portada

Los programas Java realizan las operaciones de entrada y salida a través de lo que se denominan streams (traducido: flujos).

stream

Un stream es una abstracción de todo aquello que produzca o consuma información. Podemos ver a este stream como una entidad lógica que, por otra parte, se encontrará vinculado con un dispositivo físico. La eficacia de esta forma de implementación radica en que las operaciones de entrada y salida que el programador necesita manejar son las mismas independientemente del dispositivo con el que estemos actuando. Será Java quien se encargue de manejar el dispositivo concreto, ya se trate del teclado, el monitor, un sistema de ficheros o un socket de red, etc., liberando a nuestro código de tener que saber con quién está interactuando.

1. Clasificación de los Streams

En Java los streams se materializan en un conjunto de clases y subclases, contenidas en el paquete java.io. Todas las clases para manejar streams parten, de cuatro clases abstractas:

Streams Orientados a carácter Orientados a bytes
para lectura Reader InputStream
para escritura Writer OutputStream

1.1. Streams orientados a byte (byte streams)

Proporcionan un medio adecuado para el manejo de entradas y salidas de bytes y su uso lógicamente está orientado a la lectura y escritura de datos binarios. El tratamiento del flujo de bytes viene gobernado por dos clases abstractas que son InputStream y OutputStream.

Cada una de estas clases abstractas tiene varias subclases concretas que controlan las diferencias entre los distintos dispositivos de I/O que se pueden utilizar. Así mismo, estas dos clases son las que definen los métodos que sus subclases tendrán implementados y, de entre todas, destacan las operaciones read() y write() que leen y escriben bytes de datos respectivamente.

diagrama1


diagrama2

1.2. Streams orientados a carácter (character streams)

Proporciona un medio conveniente para el manejo de entradas y salidas de caracteres. Dichos flujos usan codificación Unicode y, por tanto, se pueden internacionalizar.

Este es un modo que Java nos proporciona para manejar caracteres, pero al nivel más bajo todas las operaciones de I/O son orientadas a byte. Al igual que el anterior, el flujo de caracteres también viene gobernado por dos clases abstractas: Reader y Writer. Dichas clases manejan flujos de caracteres Unicode; y también de ellas derivan subclases concretas que implementan los métodos definidos en ellas siendo los más destacados los métodos read() y write() que, en este caso, leen y escriben caracteres de datos respectivamente.

diagrama3


diagrama4

Como hemos comentado, tanto en Reader como en InputStream encontramos un método read(), en concreto public int read(). Se observa la diferencia entre estos dos métodos nos ayudará a comprender la diferencia entre los flujos orientados a byte y los orientados a carácter.

método devuelve
int InputStream.read() valor entre 0 y 255
int Reader.read() valor entre 0 y 65535

A pesar de llamarse igual, InputStream.read() devuelve el siguiente byte de datos leído del stream. El valor que devuelve está entre 0 y 255 (ó -1 si se ha llegado al final del stream). Sin embargo, Reader.read(), devuelve un valor entre 0 y 65535 (ó -1 si se ha llegado al final del stream), correspondiente al siguiente carácter simple leído del stream.

2. Stream estándar

Existen una serie de stream de uso común a los cuales se denomina stream estándar. El sistema se encarga de crear estos stream automáticamente.

stream métodos
System.in
Instancia de la clase InputStream: flujo de bytes de entrada.
Métodos:
    - read() permite leer un byte de la entrada como entero.
    - skip(n) ignora n bytes de la entrada.
    - available() número de bytes disponibles para leer en la entrada.
System.out
Instancia de la clase PrintStream: flujo de bytes de salida.
Métodos:
    - para impresión de datos: print(), println().
    - flush() vacía el buffer de salida escribiendo su contenido..
System.err
Funcionamiento similar a System.out.
Se utiliza para enviar mensajes de error (por ejemplo a un fichero de log o a la consola).

Por defecto, System.in, System.out y System.err se encuentran asociados a la consola (teclado y pantalla), pero es posible redirigirlos a otras fuentes o destinos, como por ejemplo a un fichero.

3. Utilización de Streams

Para utilizar un stream hay que seguir una serie de pasos:

Lectura:

1º. Abrir el stream asociado a una fuente de datos (creación del objeto stream):

     - Teclado.

     - Fichero.

     - Socket remoto.

2º. Mientras existan datos disponibles:

     - Leer datos.

3º. Cerrar el stream (método close).

Escritura:

1º. Abrir el stream asociado a una fuente de datos (creación del objeto stream):

     - Pantalla.

     - Fichero.

     - Socket local.

2º. Mientras existan datos disponibles:

     - Escribir datos.

3º. Cerrar el stream (método close).

A tener en cuenta:

  • Los streams estándar ya se encarga el sistema de abrirlos y cerrarlos.
  • Un fallo en cualquier punto del proceso produce una IOException.

4. Las clases InputStream y OutputStream

Como hemos dicho anteriormente, proporcionan métodos para leer y escribir, respectivamente, un byte de información, a través de sus métodos read() y write().

clase métodos descripción
InputStream int read() Lee un byte de información y lo devuelve como un entero cuyo valor estará entre 0 y 255.
Si se detecta el final de los datos de entrada devuelve -1.
OutputStream write (int b) Escribe un byte de información en el stream. El parámetro es entero, pero si su valor es superior a 255 se escriben los 8 bits de menos peso (los más a la derecha).

5. Las clases Reader y Writer

Permiten, respectivamente, leer y escribir un carácter en el stream.

clase métodos descripción
Reader int read() Lee un carácter unicode de información y lo devuelve como un entero cuyo valor estará entre 0 y 65565.
Si se detecta el final de los datos de entrada devuelve -1.
Writer write (int c) Escribe un carácter unicode en el stream. El parámetro es entero y corresponderá al código Unicode del carácter que se escribe.

6. Las clases InputStreamReader y OutputStreamWriter

Son clases que actúan de puente entre streams orientados a bytes y streams orientados a carácter. Podemos, por ejemplo, crear un InputStreamReader asociado a un InputStream y leer caracteres del InputStream asociado, a través del InputStreamReader.

clase métodos descripción
InputStreamReader InputStreamReader(inputStream)



int read()
Constructor: El objeto se crea a partir de un InputStream (orientado a byte). Leerá información del inputStream asociado y la devolverá en forma de caracteres. Se puede indicar el charset a utilizar.


Lee un carácter del InputStream asociado.
OutputStreamWriter OutputStreamWriter(outputStream)

write(int c)
Constructor: Crea el objeto asociándolo a un outputStream, en el que escribirá bytes. Se puede indicar el charset a utilizar.


Escribe el carácter indicado en el OutputStream asociado.

7. Buffering

Las clases BufferedReader, BufferedWritter, BufferedInputStream y BufferedOutputStream permiten realizar buffering.

Situadas "por delante" de un stream acumulan las operaciones de lectura y escritura en una memoria o buffer y cuando hay suficiente información las operaciones se realizan finalmente sobre el dispositivo físico.

Mantienen las mismas operaciones de lectura y escritura que sus clases padre pero, como hemos dicho, reducen el número de accesos al dispositivo físico por el uso de buffers.

clase métodos descripción
BufferedReader


BufferedWriter



String readLine()


void write(String s)

void newLine()
Además de los métodos heredados, encontramos otros que permiten leer Strings completos, escribir una línea completa de texto y hacer saltos de línea.




BufferedInputStream

BufferedOutputStream
Mantienen las mismas operaciones de lectura y escritura que sus clases padre pero, como hemos dicho, reducen el número de accesos al dispositivo físico por el uso de buffers.

8. DataInputStream y DataOutputStream

Realizan una transformación de la información antes de ser escrita o después de ser leída. Los bytes leídos o escritos se interpretan como datos correspondientes a los tipos primitivos de Java.

clase métodos descripción
DataInputStream




DataOutputStream
read(),
readInt(),
readDouble(),
readUTF()

write(),
writeInt(),
writeDouble(),
writeUTF()
Que permiten leer y escribir información correspondiente a los distintos tipos de datos de Java.

9. PrintWriter

clase métodos descripción
PrintWriter print()
println()
Esta clase (a la que pertenece System.out) tiene los métodos print y println, que escriben en el stream de salida datos binarios representados en forma de cadenas de caracteres.

10. Combinación de Streams

En muchas ocasiones, una sola clase de las vistas no nos da la funcionalidad necesaria para poder hacer la tarea que se requiere. En tales casos es necesario combinar (anidar) varios Streams de manera que unos actúan como origen de información de los otros, o unos escriben sobre los otros.

En este caso tendríamos que combinar tres clases:

image-20220313205036000
BufferedReader b = new BufferedReader(new InputStreamReader(System.in));
Ejemplo: estándar de salida

En el siguiente ejemplo se pide introducir texto hasta que se introduzca una línea con el texto "salir". Dicho texto se almacenará en un fichero salida.txt.

El proceso debe de estar en un bloque try..catch.

package UT06.P1_Flujos;

import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class P1_2_FlujoEstandarSalida {

  public static void main(String[] args) {
    try {
      // stream de entrada
      // System.in -> captura la entrada desde la consola
      // InputStreamReader(System.in) -> convierte bytes de entrada estándar en caracteres
      // BufferedReader -> para leer líneas completas de texto
      BufferedReader br = new BufferedReader(
                                new InputStreamReader(System.in));
      // stream de salida orientada a caracteres
      PrintWriter out = new PrintWriter(new FileWriter("salida.txt", true));
      String s;
      while (!(s = br.readLine()).equals("salir")) {
         out.println(s);
      }
      out.close();

    } catch (IOException ex) {
      System.out.println("Error: " + ex.getMessage());
    }
  }
}

Un poquito de ...