1. Clase OutputStream

Recientemente exploramos flujos de entrada. Es hora de hablar de flujos de salida.

La clase OutputStream es la clase principal para todas las clases que admiten salida de bytes. Esta es una clase abstracta que por sí sola no hace nada, pero tiene clases descendientes para cada ocasión.

Esto suena extremadamente complicado. Para simplificar, esta clase opera en bytes, y no en caracteres u otros tipos de datos. Y el hecho de que sea abstracta significa que normalmente no la usamos, sino más bien una de sus clases descendientes. Por ejemplo, FileOutputStream y similares.

Pero volviendo a la clase OutputStream. Esta clase tiene métodos que todas sus clases descendientes deben implementar. Aquí están los principales:

Métodos Descripción
void write(int b)
Escribe un byte (no un int) en el stream.
void write(byte[] buffer)
Escribe un array de bytes en el stream.
void write(byte[] buffer, off, len)
Escribe parte de un array de bytes en el stream.
void flush()
Escribe todos los datos almacenados en el búfer en el flujo
void close()
Cierra el flujo

Cuando creas un objeto de una clase que hereda de InputStream, generalmente especificas un objeto fuente desde el que el InputStream lee datos. Cuando creas un objeto de una clase que hereda de OutputStream, también especificas el objeto o stream de destino al que se escribirán los datos.

Repasemos brevemente todos los métodos de la clase OutputStream:

Método write(int b)

Este método escribe un byte (no un int) en el stream de salida. El valor pasado se convierte en byte y se descartan los tres primeros bytes del int.

Método write(byte[] buffer)

Escribe el array de bytes pasado en el stream de salida. Eso es todo.

Método write(byte[] buffer, int offset, int length)

Escribe una porción del array de bytes pasado en el stream de salida. La variable de offset indica el índice del primer elemento del array, y length es la longitud del subconjunto a escribir.

Método flush()

El método flush() se utiliza para forzar a que se escriban en el stream de destino los datos potencialmente almacenados en búfer en el stream actual. Esto es relevante cuando se utiliza el almacenamiento en búfer y/o múltiples objetos stream dispuestos en una cadena.

Método close()

Escribe cualquier dato no escrito en el objeto de destino. El método close() no debe ser llamado si utilizas un bloque try-with-resources.

Ejemplo de copiar un archivo

Código Nota
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);
FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = new byte[65536]; // 64Kb
   while (input.available() > 0)
   {
      int real = input.read(buffer);
      output.write(buffer, 0, real);
   }
}



InputStream para leer desde un archivo
OutputStream para escribir en un archivo

Buffer en el cual leeremos los datos
Mientras haya datos en el flujo

Leer los datos en el buffer
Escribir los datos del buffer en el segundo flujo

2. Clase Writer

La clase Writer es exactamente igual que la clase OutputStream, pero con una sola diferencia nuevamente: trabaja con caracteres (char) en lugar de bytes.

Esta es una clase abstracta: no se pueden crear objetos de la clase Writer. Su objetivo principal es ser una clase padre común para cientos de clases descendientes y brindarles métodos comunes para trabajar con flujos de caracteres.

Métodos de la clase Writer (y todas sus clases descendientes):

Métodos Descripción
void write(int b)
Escribe un carácter (no un int) en el flujo.
void write(char[] buffer)
Escribe un arreglo de caracteres en el flujo
void write(char[] buffer, off, len)
Escribe una parte de un arreglo de caracteres en el flujo
void write(String str)
Escribe una cadena en el flujo
void write(String str, off, len)
Escribe una parte de una cadena en el flujo
void flush()
Escribe todos los datos almacenados en el búfer en el flujo
void close()
Cierra el flujo

Los métodos son muy similares a los métodos de la clase OutputStream, pero trabajan con caracteres en lugar de bytes.

Descripción de los métodos:

Método write(int b)

Este método escribe un solo carácter (char - no un int) en el flujo de salida. El valor pasado se convierte en un char y los primeros dos bytes son descartados.

Método write(char[] buffer)

Escribe el arreglo de caracteres dado en el flujo de salida.

Método write(char[] buffer, int offset, int length)

Escribe una porción del arreglo de caracteres dado en el flujo de salida. La variable offset indica el índice del primer elemento del arreglo y length es la longitud del subconjunto que se escribirá.

Método write(String str)

Escribe la cadena dada en el flujo de salida.

Método write(String str, int offset, int length)

Escribe una porción de la cadena dada en el flujo de salida: la cadena se convierte en un arreglo de caracteres. La variable offset indica el índice del primer elemento del arreglo y length es la longitud del subconjunto que se escribirá.

Método flush()

El método flush() se utiliza para forzar la escritura en el flujo de destino de cualquier dato que se encuentre en el búfer del flujo actual. Esto es relevante cuando se utiliza el almacenamiento en búfer y/o múltiples objetos de flujo dispuestos en una cadena.

Método close()

Escribe cualquier dato no escrito en el objeto de destino. No es necesario llamar al método close() si se utiliza un bloque try-with-resources.

Ejemplo de un programa que copia un archivo de texto:

Código Nota
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileReader reader = new FileReader(src);
FileWriter writer = new FileWriter(dest))
{
   char[] buffer = new char[65536]; // 128Kb
   while (reader.ready())
   {
      int real = reader.read(buffer);
      writer.write(buffer, 0, real);
   }
}



Reader para leer desde un archivo
Writer para escribir en un archivo

Un buffer donde leeremos los datos
Mientras haya datos en el stream

Leer los datos en el buffer
Escribir los datos del buffer en el segundo stream

Clase StringWriter

Existe otra clase interesante que hereda la clase Writer, se llama StringWriter. Contiene una cadena mutable, un objeto StringBuffer. Y cada vez que se "escribe" algo en el objeto StringWriter, el texto se agrega simplemente a su búfer interno.

Ejemplo:

Código Nota
StringWriter writer = new StringWriter();
writer.write("Hello");
writer.write(String.valueOf(123));

String result = writer.toString();
Se crea un flujo de caracteres de destino (StringWriter)
Se escribe una cadena en el búfer dentro de StringWriter
Se escribe una cadena en el búfer dentro de StringWriter

Conversión del contenido de un objeto en una cadena.

En este caso, la clase StringWriter es esencialmente un contenedor para la clase StringBuffer, pero la clase StringWriter es descendiente de la clase Writer, y puede usarse en cadenas de objetos de flujo. Esta es una propiedad bastante útil en la práctica.