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 Scanner
objeto — 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: InputStream
e OutputStream
.
A InputStream
classe tem um read()
método que permite ler dados dela. E a OutputStream
classe 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 OutputStream
pode escrever bytes (e matrizes de bytes) e um InputStream
objeto 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 Scanner
classe: 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 OutputStream
també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: Reader
e Writer
. A Reader
classe é análoga à InputStream
classe, mas seu read()
método não lê bytes, mas caracteres ( char
). A Writer
classe corresponde à OutputStream
classe. E assim como a Reader
classe, ela trabalha com caracteres ( char
), não com bytes.
Se compararmos essas quatro classes, obtemos a seguinte imagem:
Byte (byte) | Personagens (char) | |
---|---|---|
Lendo dados |
|
|
Escrevendo dados |
|
|
Aplicação prática
As próprias classes InputStream
, OutputStream
, Reader
e Writer
nã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. InputStream
classe
A InputStream
classe é 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 InputStream
classe e todas as suas classes descendentes:
Métodos | Descrição |
---|---|
|
Lê um byte do fluxo |
|
Lê uma matriz de bytes do fluxo |
|
Lê todos os bytes do fluxo |
|
Ignora n bytes no fluxo (lê e descarta-os) |
|
Verifica quantos bytes restam no fluxo |
|
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 int
tipo de retorno. Este tipo foi escolhido porque int
é o tipo inteiro padrão. Os três primeiros bytes do int
serão zero.
read(byte[] buffer)
método
Esta é a segunda variante do read()
método. Ele permite que você leia uma matriz de bytes InputStream
de 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 FileInputStream
classe. 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 InputStream
até 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 InputStream
objeto. 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 n
os 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 |
---|---|
|
InputStream para ler do arquivo OutputStream para 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 InputStream
para ler dados de um arquivo e FileOutputStream
é descendente de OutputStream
para gravar dados em um arquivo. Falaremos sobre a segunda aula um pouco mais tarde.
Outro ponto interessante aqui é a real
variá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 real
bytes. Isso é exatamente o que acontece no write()
método.
3. Reader
classe
A Reader
classe é um análogo completo da InputStream
classe. A única diferença é que trabalha com caracteres ( char
), não com bytes. Assim como a InputStream
classe, a Reader
classe 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 Reader
classe (e todas as suas classes descendentes):
Métodos | Descrição |
---|---|
|
Lê um char do stream |
|
Lê uma char matriz do fluxo |
|
Ignora n chars o fluxo (lê e descarta) |
|
Verifica se ainda há algo no fluxo |
|
Fecha o fluxo |
Os métodos são muito semelhantes aos da InputStream
classe, embora existam pequenas diferenças.
int read()
método
Esse método lê um char
do fluxo e o retorna. O char
tipo 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 Reader
uma 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 Reader
objeto. Funciona exatamente da mesma forma que o método análogo da InputStream
classe. Retorna o número de caracteres que foram realmente ignorados.
boolean ready()
método
Retorna true
se 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 |
---|---|
|
Reader para ler de um arquivo Writer para 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 |
GO TO FULL VERSION