1. Fluxos de dados

Raramente um programa existe como uma ilha em si. Os programas geralmente interagem de alguma forma com o "mundo exterior". Isso pode acontecer por meio da leitura de dados do teclado, envio de mensagens, download de páginas da Internet ou, inversamente, upload de arquivos para um servidor remoto.

Podemos nos referir a todos esses comportamentos em uma palavra: troca de dados entre o programa e o mundo exterior. Espere, isso não é apenas uma palavra.

Obviamente, a própria troca de dados pode ser dividida em duas partes: receber dados e enviar dados. Por exemplo, você lê dados do teclado usando um Scannerobjeto — isso é receber dados. E você exibe dados na tela usando um System.out.println()comando - isso é enviar dados.

Na programação, o termo "fluxo" é usado para descrever a troca de dados. De onde veio esse termo?

Na vida real, você pode ter um fluxo de água ou um fluxo de consciência. Na programação, temos fluxos de dados .

Os fluxos são uma ferramenta versátil. Eles permitem que o programa receba dados de qualquer lugar (fluxos de entrada) e envie dados para qualquer lugar (fluxos de saída). Assim, existem dois tipos:

  • Um fluxo de entrada é para receber dados
  • Um fluxo de saída é para enviar dados

Para tornar os fluxos 'tangíveis', os criadores de Java escreveram duas classes: InputStreame OutputStream.

A InputStreamclasse tem um read()método que permite ler dados dela. E a OutputStreamclasse tem um write()método que permite gravar dados nela. Eles também têm outros métodos, mas falaremos mais sobre isso depois.

fluxos de bytes

De que tipo de dados estamos falando? Que formato leva? Em outras palavras, quais tipos de dados essas classes suportam?

Essas são classes genéricas, portanto, oferecem suporte ao tipo de dados mais comum — o byte. Um OutputStreampode escrever bytes (e matrizes de bytes) e um InputStreamobjeto pode ler bytes (ou matrizes de bytes). É isso - eles não suportam nenhum outro tipo de dados.

Como resultado, esses fluxos também são chamados de fluxos de bytes .

Uma característica dos fluxos é que seus dados só podem ser lidos (ou gravados) sequencialmente. Você não pode ler os dados do meio de um stream sem ler todos os dados que vêm antes dele.

É assim que a leitura de dados do teclado funciona na Scannerclasse: você lê os dados do teclado sequencialmente, linha por linha. Lemos uma linha, depois a próxima linha, depois a próxima linha e assim por diante. Apropriadamente, o método para ler linhas é chamado nextLine().

A gravação de dados em um OutputStreamtambém ocorre sequencialmente. Um bom exemplo disso é a saída do console. Você produz uma linha, seguida por outra e outra. Esta é a saída sequencial. Você não pode produzir a primeira linha, depois a décima e depois a segunda. Todos os dados são gravados em um fluxo de saída apenas sequencialmente.

Fluxos de caracteres

Você aprendeu recentemente que as strings são o segundo tipo de dados mais popular e, de fato, são. Muitas informações são passadas na forma de caracteres e strings inteiras. Um computador é excelente em enviar e receber tudo como bytes, mas os humanos não são tão perfeitos.

Considerando esse fato, os programadores Java escreveram mais duas classes: Readere Writer. A Readerclasse é análoga à InputStreamclasse, mas seu read()método não lê bytes, mas caracteres ( char). A Writerclasse corresponde à OutputStreamclasse. E assim como a Readerclasse, ela trabalha com caracteres ( char), não com bytes.

Se compararmos essas quatro classes, obtemos a seguinte imagem:

Byte (byte) Personagens (char)
Lendo dados
InputStream
Reader
Escrevendo dados
OutputStream
Writer

Aplicação prática

As próprias classes InputStream, OutputStream, Readere Writernão são usadas diretamente por ninguém, pois não estão associadas a nenhum objeto concreto do qual os dados possam ser lidos (ou nos quais os dados possam ser gravados). Mas essas quatro classes têm muitas classes descendentes que podem fazer muito.


2. InputStreamclasse

A InputStreamclasse é interessante porque é a classe pai de centenas de classes descendentes. Ele não possui nenhum dado próprio, mas possui métodos herdados por todas as suas classes derivadas.

Em geral, é raro que objetos de fluxo armazenem dados internamente. Um fluxo é uma ferramenta para ler/gravar dados, mas não para armazenamento. Dito isto, há exceções.

Métodos da InputStreamclasse e todas as suas classes descendentes:

Métodos Descrição
int read()
Lê um byte do fluxo
int read(byte[] buffer)
Lê uma matriz de bytes do fluxo
byte[] readAllBytes()
Lê todos os bytes do fluxo
long skip(long n)
Ignora nbytes no fluxo (lê e descarta-os)
int available()
Verifica quantos bytes restam no fluxo
void close()
Fecha o fluxo

Vamos passar brevemente por esses métodos:

read()método

O read()método lê um byte do fluxo e o retorna. Você pode estar confuso com o inttipo de retorno. Este tipo foi escolhido porque inté o tipo inteiro padrão. Os três primeiros bytes do intserão zero.

read(byte[] buffer)método

Esta é a segunda variante do read()método. Ele permite que você leia uma matriz de bytes InputStreamde uma vez. O array que irá armazenar os bytes deve ser passado como argumento. O método retorna um número — o número de bytes realmente lidos.

Digamos que você tenha um buffer de 10 kilobytes e esteja lendo dados de um arquivo usando a FileInputStreamclasse. Se o arquivo contiver apenas 2 kilobytes, todos os dados serão carregados no array do buffer, e o método retornará o número 2048 (2 kilobytes).

readAllBytes()método

Um método muito bom. Ele apenas lê todos os dados do InputStreamaté que se esgote e os retorna como uma matriz de byte único. Isso é muito útil para ler arquivos pequenos. Arquivos grandes podem não caber fisicamente na memória e o método lançará uma exceção.

skip(long n)método

Este método permite pular os primeiros n bytes do InputStreamobjeto. Como os dados são lidos estritamente sequencialmente, esse método simplesmente lê os primeiros n bytes do fluxo e os descarta.

Retorna o número de bytes que foram realmente ignorados (caso o fluxo tenha terminado antes que nos bytes fossem ignorados).

int available()método

O método retorna o número de bytes que ainda restam no fluxo

void close()método

O close()método fecha o fluxo de dados e libera os recursos externos associados a ele. Depois que um fluxo é fechado, nenhum outro dado pode ser lido dele.

Vamos escrever um programa de exemplo que copia um arquivo muito grande. Não podemos usar o readAllBytes()método para ler o arquivo inteiro na memória. Exemplo:

Código Observação
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 ler do arquivo
OutputStreampara gravar no

arquivo Buffer no qual leremos os dados
Enquanto houver dados no fluxo

Ler dados no buffer
Gravar os dados do buffer no segundo fluxo

Neste exemplo, usamos duas classes: FileInputStreamé descendente de InputStreampara ler dados de um arquivo e FileOutputStreamé descendente de OutputStreampara gravar dados em um arquivo. Falaremos sobre a segunda aula um pouco mais tarde.

Outro ponto interessante aqui é a realvariável. Quando o último bloco de dados é lido de um arquivo, ele pode facilmente ter menos de 64 KB de dados. Conseqüentemente, precisamos enviar não todo o buffer, mas apenas parte dele - os primeiros realbytes. Isso é exatamente o que acontece no write()método.



3. Readerclasse

A Readerclasse é um análogo completo da InputStreamclasse. A única diferença é que trabalha com caracteres ( char), não com bytes. Assim como a InputStreamclasse, a Readerclasse não é usada sozinha em nenhum lugar: ela é a classe pai para centenas de classes descendentes e define métodos comuns para todas elas.

Métodos da Readerclasse (e todas as suas classes descendentes):

Métodos Descrição
int read()
Lê um chardo stream
int read(char[] buffer)
Lê uma charmatriz do fluxo
long skip(long n)
Ignora n charso fluxo (lê e descarta)
boolean ready()
Verifica se ainda há algo no fluxo
void close()
Fecha o fluxo

Os métodos são muito semelhantes aos da InputStreamclasse, embora existam pequenas diferenças.

int read()método

Esse método lê um chardo fluxo e o retorna. O chartipo aumenta para um int, mas os dois primeiros bytes do resultado são sempre zero.

int read(char[] buffer)método

Esta é a segunda variante do read()método. Ele permite que você leia uma matriz de caracteres de Readeruma vez. O array que irá armazenar os caracteres deve ser passado como argumento. O método retorna um número — o número de caracteres realmente lidos.

skip(long n)método

Este método permite pular os primeiros n caracteres do Readerobjeto. Funciona exatamente da mesma forma que o método análogo da InputStreamclasse. Retorna o número de caracteres que foram realmente ignorados.

boolean ready()método

Retorna truese houver bytes não lidos no fluxo.

void close()método

O close()método fecha o fluxo de dados e libera os recursos externos associados a ele. Depois que um fluxo é fechado, nenhum outro dado pode ser lido dele.

Para comparação, vamos escrever um programa que copia um arquivo de texto:

Código Observação
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 ler de um arquivo
Writerpara gravar em um arquivo

Buffer no qual leremos os dados
Enquanto houver dados no fluxo

Ler dados em um buffer
Gravar os dados do buffer no segundo fluxo