1. Flujos de datos

Rara vez existe un programa como una isla en sí mismo. Los programas generalmente interactúan de alguna manera con el "mundo exterior". Esto puede suceder mediante la lectura de datos del teclado, el envío de mensajes, la descarga de páginas de Internet o, por el contrario, la carga de archivos en un servidor remoto.

Podemos referirnos a todos estos comportamientos en una sola palabra: intercambio de datos entre el programa y el mundo exterior. Espera, eso no es solo una palabra.

Por supuesto, el intercambio de datos en sí se puede dividir en dos partes: recibir datos y enviar datos. Por ejemplo, lee datos del teclado usando un Scannerobjeto: esto es recibir datos. Y muestra datos en la pantalla usando un System.out.println()comando: esto es enviar datos.

En programación, el término "flujo" se utiliza para describir el intercambio de datos. ¿De dónde viene ese término?

En la vida real, puedes tener un chorro de agua o un chorro de conciencia. En programación, tenemos flujos de datos .

Los streams son una herramienta versátil. Permiten que el programa reciba datos desde cualquier lugar (flujos de entrada) y envíe datos a cualquier lugar (flujos de salida). Así, hay dos tipos:

  • Un flujo de entrada es para recibir datos.
  • Un flujo de salida es para enviar datos.

Para hacer que los flujos sean 'tangibles', los creadores de Java escribieron dos clases: InputStreamy OutputStream.

La InputStreamclase tiene un read()método que le permite leer datos de ella. Y la OutputStreamclase tiene un write()método que le permite escribir datos en ella. También tienen otros métodos, pero hablaremos de eso más adelante.

flujos de bytes

¿De qué tipo de datos estamos hablando? Que formato lleva? En otras palabras, ¿qué tipos de datos admiten estas clases?

Estas son clases genéricas, por lo que admiten el tipo de datos más común: el byte. Un OutputStreampuede escribir bytes (y matrices de bytes), y un InputStreamobjeto puede leer bytes (o matrices de bytes). Eso es todo: no admiten ningún otro tipo de datos.

Como resultado, estos flujos también se denominan flujos de bytes .

Una característica de los flujos es que sus datos solo se pueden leer (o escribir) secuencialmente. No puede leer datos desde el medio de una secuencia sin leer todos los datos que vienen antes.

Así es como funciona la lectura de datos del teclado a través de la Scannerclase: lee los datos del teclado secuencialmente, línea por línea. Leemos una línea, luego la siguiente, luego la siguiente y así sucesivamente. Oportunamente, el método para leer líneas se llama nextLine().

La escritura de datos en un OutputStreamtambién ocurre secuencialmente. Un buen ejemplo de esto es la salida de la consola. Da salida a una línea, seguida de otra y otra. Esta es una salida secuencial. No puede generar la primera línea, luego la décima y luego la segunda. Todos los datos se escriben en un flujo de salida solo de forma secuencial.

flujos de caracteres

Recientemente aprendió que las cadenas son el segundo tipo de datos más popular y, de hecho, lo son. Se transmite mucha información en forma de caracteres y cadenas completas. Una computadora es excelente para enviar y recibir todo como bytes, pero los humanos no son tan perfectos.

Teniendo en cuenta este hecho, los programadores de Java escribieron dos clases más: Readery Writer. La Readerclase es análoga a la InputStreamclase, pero su read()método no lee bytes, sino caracteres ( char). La Writerclase corresponde a la OutputStreamclase. Y al igual que la Readerclase, funciona con caracteres ( char), no con bytes.

Si comparamos estas cuatro clases, obtenemos la siguiente imagen:

bytes (byte) Caracteres (caracteres)
Lectura de datos
InputStream
Reader
Escribir datos
OutputStream
Writer

Aplicación práctica

Las clases InputStream, y en sí mismas no son utilizadas directamente por nadie, ya que no están asociadas con ningún objeto concreto desde el cual se puedan leer datos (o en los que se puedan escribir datos) OutputStream. Pero estas cuatro clases tienen muchas clases descendientes que pueden hacer mucho.ReaderWriter


2. InputStreamclase

La InputStreamclase es interesante porque es la clase principal de cientos de clases descendientes. No tiene ningún dato propio, pero tiene métodos que heredan todas sus clases derivadas.

En general, es raro que los objetos de flujo almacenen datos internamente. Un flujo es una herramienta para leer/escribir datos, pero no para almacenar. Dicho esto, hay excepciones.

Métodos de la InputStreamclase y todas sus clases descendientes:

Métodos Descripción
int read()
Lee un byte de la secuencia
int read(byte[] buffer)
Lee una matriz de bytes de la secuencia
byte[] readAllBytes()
Lee todos los bytes de la secuencia
long skip(long n)
Salta nbytes en el flujo (los lee y los descarta)
int available()
Comprueba cuántos bytes quedan en el flujo
void close()
Cierra la corriente

Repasemos brevemente estos métodos:

read()método

El read()método lee un byte del flujo y lo devuelve. Es posible que se confunda con el inttipo de devolución. Se eligió este tipo porque intes el tipo entero estándar. Los tres primeros bytes de intserán cero.

read(byte[] buffer)método

Esta es la segunda variante del read()método. Le permite leer una matriz de bytes InputStreamde una vez. La matriz que almacenará los bytes debe pasarse como argumento. El método devuelve un número: el número de bytes realmente leídos.

Digamos que tiene un búfer de 10 kilobytes y está leyendo datos de un archivo usando la FileInputStreamclase. Si el archivo contiene solo 2 kilobytes, todos los datos se cargarán en la matriz de búfer y el método devolverá el número 2048 (2 kilobytes).

readAllBytes()método

Un muy buen método. Simplemente lee todos los datos InputStreamhasta que se agota y los devuelve como una matriz de un solo byte. Esto es muy útil para leer archivos pequeños. Es posible que los archivos grandes no quepan físicamente en la memoria y el método generará una excepción.

skip(long n)método

Este método le permite omitir los primeros n bytes del InputStreamobjeto. Debido a que los datos se leen de manera estrictamente secuencial, este método simplemente lee los primeros n bytes del flujo y los descarta.

Devuelve el número de bytes que realmente se omitieron (en caso de que la transmisión finalice antes de que nse omitan los bytes).

int available()método

El método devuelve el número de bytes que aún quedan en el flujo

void close()método

El close()método cierra el flujo de datos y libera los recursos externos asociados con él. Una vez que se cierra una secuencia, no se pueden leer más datos de ella.

Escribamos un programa de ejemplo que copie un archivo muy grande. No podemos usar el readAllBytes()método para leer todo el archivo en la memoria. Ejemplo:

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);
   }
}



InputStreampara leer del archivo
OutputStreampara escribir en el archivo

Búfer en el que leeremos los datos
Siempre que haya datos en la secuencia

Leer datos en el búfer
Escribir los datos del búfer en la segunda secuencia

En este ejemplo, usamos dos clases: FileInputStreames descendiente de InputStreampara leer datos de un archivo y FileOutputStreames descendiente de OutputStreampara escribir datos en un archivo. Hablaremos de la segunda clase un poco más tarde.

Otro punto interesante aquí es la realvariable. Cuando se lee el último bloque de datos de un archivo, fácilmente podría tener menos de 64 KB de datos. En consecuencia, no necesitamos generar todo el búfer, sino solo una parte: los primeros realbytes. Esto es exactamente lo que sucede en el write()método.



3. Readerclase

La Readerclase es un análogo completo de la InputStreamclase. La única diferencia es que funciona con caracteres ( char), no con bytes. Al igual que la InputStreamclase, la Readerclase no se usa en ninguna parte por sí sola: es la clase principal para cientos de clases descendientes y define métodos comunes para todas ellas.

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

Métodos Descripción
int read()
Lee uno charde la corriente
int read(char[] buffer)
Lee una charmatriz de la secuencia
long skip(long n)
Salta n charsen la corriente (los lee y los descarta)
boolean ready()
Comprueba si todavía queda algo en la corriente
void close()
Cierra la corriente

Los métodos son muy similares a los de la InputStreamclase, aunque hay ligeras diferencias.

int read()método

Este método lee uno charde la secuencia y lo devuelve. El chartipo se amplía a un int, pero los dos primeros bytes del resultado siempre son cero.

int read(char[] buffer)método

Esta es la segunda variante del read()método. Le permite leer una matriz de caracteres de Readeruna vez. La matriz que almacenará los caracteres debe pasarse como argumento. El método devuelve un número: el número de caracteres que realmente se leen.

skip(long n)método

Este método le permite omitir los primeros n caracteres del Readerobjeto. Funciona exactamente igual que el método análogo de la InputStreamclase. Devuelve el número de caracteres que realmente se omitieron.

boolean ready()método

Devuelve truesi hay bytes no leídos en la secuencia.

void close()método

El close()método cierra el flujo de datos y libera los recursos externos asociados con él. Una vez que se cierra una secuencia, no se pueden leer más datos de ella.

A modo de comparación, escribamos un programa que copie 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);
   }
}



Readerpara leer de un archivo
Writerpara escribir en un archivo

Búfer en el que leeremos los datos
Siempre que haya datos en el flujo

Leer datos en un búfer
Escribir los datos del búfer en el segundo flujo