Fundamentos de entrada y salida

Un stream  es un concepto  que utilizamos  para definir  un flujo de datos  desde  un origen a un destino.

El stream de origen (source stream) es donde se inicia el flujo de datos. También se utiliza el término input stream.

El stream de destino (sink stream) es donde finaliza el flujo de datos. Podemos reconocerlos como output stream.


Java dispone de dos grupos de flujos de datos: 

1. Flujos de entrada o lectura (input streams): los datos fluyen desde el fichero o dispositivo hacia el programa. 

2. Flujos de salida o escritura (output streams): los datos fluyen desde el programa hacia el fichero o dispositivo. 


Tanto los stream de origen como los stream de destino se conocen como nodos (node stream). A lo largo de este tema veremos que hay muchos tipos de nodos: ficheros, memoria, pipes, …etc.


Todo proceso de lectura o escritura de datos consta de tres pasos: 

1. Abrir el flujo de datos de lectura o de escritura. 

2. Leer o escribir datos a través del flujo abierto. 

3. Cerrar el flujo de datos. 


Los streams pueden ser de dos tipos:

  • A nivel  de  bytes;  Tienen  la  ventaja  de  poder  procesar  datos  de  cualquier formato pero el inconveniente es que requieren ajustarse a otros formatos.
  • A nivel de caracteres; La ventaja es que tenemos un formato textual pero está limitado únicamente a este formato.

La siguiente tabla recoge las clases principales para manejar streams.

FLUJOS DE BYTES

La clase InputStream tiene los siguientes métodos: Métodos de lectura:

  • int read(); Devuelve el numero de bytes leídos o -1 que indica fin de archivo.
  • int read(byte[] buffer); Los bytes leídos los almacena en el array buffer. Igual que antes devuelve un entero con el numero de bytes leídos o el valor -1.
  • int read(byte[]  buffer,  int  offset,  int  length);  Los  dos  argumentos  indican  la posición de inicio y la longitud dentro del array.

Otros métodos importantes:

  • void close(); Cierra el flujo.
  • int available(); Indica el numero de bytes disponibles para su lectura.
  • long skip(long n); Este método descarta el numero de bytes especificado.

Los métodos de la clase OutputStream son: Métodos de escritura:

  • void write(int c)
  • void write(byte[] buffer)
  • void write(byte[] buffer, int offset, int length) Otros métodos:
  • void close()
  • void flush(); permite forzar las escrituras.

 

FLUJOS DE CARACTERES

Los métodos de la clase Reader son: Métodos de lectura:

  • int read()
  • int read(char[] cbuf)
  • int read(char[] cbuf, int offset, int length) Otros métodos:
  • void close()
  • boolean ready()
  • long skip(long n)

Los métodos de la clase Writer: Métodos de escritura:

  • void write(int c)
  • void write(char[] cbuf)
  • void write(char[] cbuf, int offset, int length)
  • void write(String string)
  • void write(String string, int offset, int length) Otros métodos:
  • void close()
  • void flush()

CLASES IMPORTANTES

A continuación presentamos una tabla con las clases más importantes para manejar distintos tipos de nodos:

Un nodo Pipe o tubería  es una conexión  entre dos streams.  Se suele utilizar  para desviar el flujo de datos de un stream a otro.

Todas las clases que representan flujos de datos (streams) en formato binario derivan de las clases abstractas InputStream y OutputStream. En la imagen hemos reflejado las clases más importantes dentro de esta categoría. Igual que ocurría con los flujos de caracteres Unicode, los flujos de bytes también se subdividen en: 

  • Iniciadores: vuelcan o recogen datos directamente del dispositivo. 
  • Filtros: se sitúan entre el stream iniciador y el programa. 

  • InputStream:  Clase abstracta de la que derivan todas las clases que representan flujos de entrada de bytes. 
  • FileInputStream: Permite leer bytes de un fichero. Actúa como iniciador. 
  • FilterInputStream: Clase base de la que derivan las siguientes subclases que actúan como filtro, mejorando las operaciones de lectura: BufferedInputStream y DataInputStream. 
  • ObjectInputStream: Permite la lectura de objetos guardados en disco. Actúa como filtro y requiere un iniciador, por ejemplo un FileInputStream. 
  • BufferedInputStream: Permite mejorar la eficiencia de la lectura de un fichero proporcionando un buffer. Trabaja en colaboración con un objeto iniciador, por ejemplo un FileInputStream. 
  • DataInputStream: DataInputStream permite leer datos de un fichero recogiéndolos directamente como tipos de datos primitivos (int, float, double, etc.). Actúa como filtro y trabaja en colaboración con otra clase iniciadora como FileInputStream. 
  • OutputStream: Clase abstracta de la que derivan todas las clases que representan flujos de salida de bytes. 
  • FileOutputStream: Permite escribir bytes en un fichero. Actúa como iniciador. 
  • FilterOutputStream: Clase base de la que derivan las siguientes subclases que actúan como filtro, mejorando las operaciones de escritura: BufferedOutputStream y DataOutputStream. 
  • ObjectOutputStream: Permite la escritura de objetos en disco. Actúa como filtro y requiere un iniciador, por ejemplo un FileOutputStream. 
  • BufferedOutStream: Permite mejorar la eficiencia de la escritura en un fichero proporcionando un buffer. Trabaja en colaboración con un objeto iniciador, por ejemplo un FileOutputStream. 
  • DataOutputStream: Permite escribir datos en un fichero directamente como tipos de datos primitivos (int, float, double, etc.). Actúa como filtro y trabaja en colaboración con otra clase iniciadora como FileOutputStream. 

 

JERARQUIA  DE CLASES READER

JERARQUIA  DE CLASES WRITER

Ejemplo de lectura y escritura

el siguiente ejemplo es similar al anterior pero en vez de utilizar un buffer manual utilizamos las clases BufferedReader y BufferedWriter.

Estas clases nos permiten acumular datos en la lectura y en la escritura. Gracias a esto podemos escribir línea a línea en vez de carácter a carácter.

Código de ejemplo lectura y escritura de archivos