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 Scanner
objeto: 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: InputStream
y OutputStream
.
La InputStream
clase tiene un read()
método que le permite leer datos de ella. Y la OutputStream
clase 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 OutputStream
puede escribir bytes (y matrices de bytes), y un InputStream
objeto 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 Scanner
clase: 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 OutputStream
tambié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: Reader
y Writer
. La Reader
clase es análoga a la InputStream
clase, pero su read()
método no lee bytes, sino caracteres ( char
). La Writer
clase corresponde a la OutputStream
clase. Y al igual que la Reader
clase, funciona con caracteres ( char
), no con bytes.
Si comparamos estas cuatro clases, obtenemos la siguiente imagen:
bytes (byte) | Caracteres (caracteres) | |
---|---|---|
Lectura de datos |
|
|
Escribir datos |
|
|
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.Reader
Writer
2. InputStream
clase
La InputStream
clase 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 InputStream
clase y todas sus clases descendientes:
Métodos | Descripción |
---|---|
|
Lee un byte de la secuencia |
|
Lee una matriz de bytes de la secuencia |
|
Lee todos los bytes de la secuencia |
|
Salta n bytes en el flujo (los lee y los descarta) |
|
Comprueba cuántos bytes quedan en el flujo |
|
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 int
tipo de devolución. Se eligió este tipo porque int
es el tipo entero estándar. Los tres primeros bytes de int
serán cero.
read(byte[] buffer)
método
Esta es la segunda variante del read()
método. Le permite leer una matriz de bytes InputStream
de 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 FileInputStream
clase. 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 InputStream
hasta 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 InputStream
objeto. 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 n
se 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 |
---|---|
|
InputStream para leer del archivo OutputStream para 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: FileInputStream
es descendiente de InputStream
para leer datos de un archivo y FileOutputStream
es descendiente de OutputStream
para escribir datos en un archivo. Hablaremos de la segunda clase un poco más tarde.
Otro punto interesante aquí es la real
variable. 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 real
bytes. Esto es exactamente lo que sucede en el write()
método.
3. Reader
clase
La Reader
clase es un análogo completo de la InputStream
clase. La única diferencia es que funciona con caracteres ( char
), no con bytes. Al igual que la InputStream
clase, la Reader
clase 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 Reader
clase (y todas sus clases descendientes):
Métodos | Descripción |
---|---|
|
Lee uno char de la corriente |
|
Lee una char matriz de la secuencia |
|
Salta n chars en la corriente (los lee y los descarta) |
|
Comprueba si todavía queda algo en la corriente |
|
Cierra la corriente |
Los métodos son muy similares a los de la InputStream
clase, aunque hay ligeras diferencias.
int read()
método
Este método lee uno char
de la secuencia y lo devuelve. El char
tipo 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 Reader
una 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 Reader
objeto. Funciona exactamente igual que el método análogo de la InputStream
clase. Devuelve el número de caracteres que realmente se omitieron.
boolean ready()
método
Devuelve true
si 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 |
---|---|
|
Reader para leer de un archivo Writer para 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 |
GO TO FULL VERSION