"¡Hola, amigo! Hoy vamos a profundizar una vez más en cómo funcionan InputStream y OutputStream . La explicación inicial fue en realidad un poco simplista. Estas no son interfaces. Son clases abstractas e incluso tienen un par de métodos implementados. Echemos un vistazo a los métodos que tienen:"

Métodos de flujo de entrada que hace el metodo
int read(byte[] buff);
Este método lee inmediatamente un bloque de bytes en el búfer ( matriz de bytes ), hasta que el búfer está lleno o hasta que la fuente no tiene más bytes para leer.
El método devuelve la cantidad de bytes realmente leídos (que puede ser menor que la longitud de la matriz)
int read();
Este método lee un byte y lo devuelve. El resultado se amplía a un int para looks. Si no hay más bytes para leer, el método devuelve -1.
int available();
Este método devuelve el número de bytes no leídos (disponibles).
void close();
Este método «cierra» el flujo. Llamas a esto cuando hayas terminado de trabajar con la transmisión.
Luego, el objeto realiza las operaciones de limpieza necesarias para cerrar el archivo, etc.
En este punto, no puede leer más datos de la secuencia.

"¿Entonces podemos leer no solo bytes individuales, sino también bloques completos?"

"Exactamente."

"¿Podemos escribir también bloques enteros?"

"Sí, échale un vistazo:"

Métodos de flujo de salida que hace el metodo
void write(int c);
Este método escribe un byte. El tipo int se reduce a un byte. La parte extra simplemente se descarta.
void write(byte[] buff);
Este método escribe un bloque de bytes.
void write(byte[] buff, int from, int count);
Este método escribe parte de un bloque de bytes. Se utiliza en los casos en que la matriz de bytes no se haya llenado por completo.
void flush();
Si la secuencia almacena internamente cualquier dato que aún no se haya escrito, este método obliga a que se escriba.
void close();
Este método «cierra» el flujo. Llamas a esto cuando hayas terminado de trabajar con la transmisión.
Luego, el objeto realiza las operaciones de limpieza necesarias para cerrar el archivo, etc. Ya no puede escribir datos en la secuencia y se llama automáticamente al vaciado.

"¿Cómo se vería el código de copia de archivos si leemos bloques completos a la vez en lugar de bytes individuales?"

"Hmm. Algo como esto:"

Copiar un archivo en el disco
public static void main(String[] args) throws Exception
{
 //Create a stream to read bytes from a file
 FileInputStream inputStream = new FileInputStream("c:/data.txt");
 //Create a stream to write bytes to a file
 FileOutputStream outputStream = new FileOutputStream("c:/result.txt");

  byte[] buffer = new byte[1000];
 while (inputStream.available() > 0) //as long as there are unread bytes
 {
  //Read the next block of bytes into buffer, and store the actual number of bytes read in count.
  int count = inputStream.read(buffer);
  outputStream.write(buffer, 0, count); //Write a block (part of a block) to the second stream
 }

 inputStream.close(); //Close both streams. We don't need them any more.
 outputStream.close();
}

"Entiendo todo sobre el búfer, pero ¿qué es esta variable de conteo?"

"Cuando leemos el último bloque de datos de un archivo, podemos obtener, digamos, 328 bytes en lugar de 1000. Por lo tanto, cuando escribimos los datos, debemos indicar que no estamos escribiendo el bloque completo, solo los primeros 328 bytes".

Cuando leemos el último bloque, el método de lectura devolverá la cantidad de bytes que realmente se leyeron. 1000 cada vez que leemos un bloque, excepto el último bloque, cuando obtenemos 328.

Entonces, cuando escribimos un bloque, indicamos que no se deben escribir todos los bytes en el búfer, solo 328 (es decir, el valor almacenado en la variable de conteo).

"Ahora todo está claro. Gracias, Ellie".