"Olá, amigo! Hoje vamos nos aprofundar mais uma vez em como InputStream e OutputStream funcionam. A explicação inicial foi realmente um pouco simplista. Não são interfaces. São classes abstratas e até têm alguns métodos implementados. Vamos dar uma olhada nos métodos que eles têm:"

Métodos InputStream O que o método faz
int read(byte[] buff);
Este método lê imediatamente um bloco de bytes no buffer ( array de bytes ), até que o buffer esteja cheio ou até que a fonte não tenha mais nenhum byte para ler.
O método retorna o número de bytes realmente lidos (que pode ser menor que o tamanho do array)
int read();
Este método lê um byte e o retorna. O resultado é ampliado para um int para looks. Se não houver mais bytes para ler, o método retorna -1.
int available();
Este método retorna o número de bytes não lidos (disponíveis).
void close();
Este método «fecha» o stream. Você chama isso quando terminar de trabalhar com o fluxo.
O objeto então executa as operações de manutenção necessárias para fechar o arquivo, etc.
Neste ponto, você não pode ler mais dados do fluxo.

"Então podemos ler não apenas bytes individuais, mas também blocos inteiros?"

"Exatamente."

"Podemos também escrever blocos inteiros?"

"Sim, confira:"

Métodos OutputStream O que o método faz
void write(int c);
Este método grava um byte. O tipo int é reduzido a um byte. A parte extra é simplesmente descartada.
void write(byte[] buff);
Este método grava um bloco de bytes.
void write(byte[] buff, int from, int count);
Este método escreve parte de um bloco de bytes. É usado nos casos em que a matriz de bytes pode não ter sido totalmente preenchida.
void flush();
Se o fluxo estiver armazenando internamente quaisquer dados que ainda não foram gravados, esse método forçará a gravação.
void close();
Este método «fecha» o stream. Você chama isso quando terminar de trabalhar com o fluxo.
O objeto então executa as operações de limpeza necessárias para fechar o arquivo, etc. Você não pode mais gravar dados no fluxo, e o flush é chamado automaticamente.

"Como seria o código de cópia de arquivo se lêssemos blocos inteiros de uma vez em vez de bytes únicos?"

"Hmm. Algo assim:"

Copiar um arquivo no 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();
}

"Eu entendo tudo sobre o buffer, mas o que é essa variável de contagem?"

"Quando lemos o último bloco de dados de um arquivo, podemos obter, digamos, 328 bytes em vez de 1.000. Portanto, quando escrevemos os dados, precisamos indicar que não estamos gravando o bloco inteiro - apenas os primeiros 328 byte."

Quando lermos o último bloco, o método read retornará o número de bytes realmente lidos. 1000 toda vez que lemos um bloco, exceto no último bloco, quando obtemos 328.

Assim, quando escrevemos um bloco, indicamos que nem todos os bytes do buffer devem ser escritos, apenas 328 (ou seja, o valor armazenado na variável de contagem).

"Agora está tudo claro. Obrigado, Ellie."