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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
public static void main(String[] args) { try{ FileReader input = new FileReader("prueba.txt"); try{ FileWriter output = new FileWriter("salida.txt"); try{ char[] buffer = new char[128]; // Caracteres leidos int numLeidos = 0; // Efectuamos la primera lectura numLeidos = input.read(buffer); while(numLeidos != -1){ output.write(buffer, 0, numLeidos); // Efectuamos la siguiente lectura numLeidos = input.read(buffer); } }finally{ // Forzar si hubiese algo pendiente de escribir output.flush(); // cerrar el fichero output.close(); } }catch(IOException ex){ ex.printStackTrace(); }finally{ input.close(); } } catch(IOException ex){ ex.printStackTrace(); } } } |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
package leerescribirfichero; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class MainBuffer { public static void main(String[] args) { try{ FileReader input = new FileReader("prueba.txt"); BufferedReader bufferEntrada = new BufferedReader(input); try{ FileWriter output = new FileWriter("salida.txt"); BufferedWriter bufferSalida = new BufferedWriter(output); try{ String linea; // Efectuamos la primera lectura linea = bufferEntrada.readLine(); while(linea != null){ bufferSalida.write(linea, 0, linea.length()); // provocamos un salto de linea bufferSalida.newLine(); // Efectuamos la siguiente lectura linea = bufferEntrada.readLine(); } }finally{ // Forzar si hubiese algo pendiente de escribir bufferSalida.flush(); // cerrar el fichero bufferSalida.close(); } }catch(IOException ex){ ex.printStackTrace(); }finally{ bufferEntrada.close(); } } catch(IOException ex){ ex.printStackTrace(); } } } |