41bb726c

Dia 19

Correntes e entrada-saída

Charles L. Perkins e Laura Lemay


CONTEÚDOS

O pacote java.io, parte da biblioteca de classe de Java padrão, fornece um grande número de classes projetadas para tratar a entrada e saída com arquivos, conexões de rede e outras fontes. Estas classes de entrada-saída conhecem-se como correntes e fornecem a funcionalidade para ler e escrever dados de vários modos. Adquiriu um vislumbre destas classes no Dia 14, "O Windows, a Ligação em rede e Outros Petiscos", quando abrimos uma conexão de rede a um arquivo e lemos os conteúdos em um applet.

Hoje explorará classes de entrada e saída de Java:

Também aprenderá aproximadamente duas interfaces de corrente que fazem a leitura e escrita de correntes datilografadas muito mais fáceis (bem como sobre vária utilidade classes costumaram acessar o sistema de arquivos).

O que são correntes?

Uma corrente é um caminho da comunicação entre a fonte de alguma informação e o seu destino. Esta informação pode vir de um arquivo, a memória do computador, ou até da Internet. De fato, a fonte e o destino de uma corrente são produtores completamente arbitrais e consumidores de bytes, respectivamente - não precisa de saber sobre a fonte da informação lendo em uma corrente, e não precisa de saber sobre o destino final escrevendo a um.

Uma corrente é um caminho da comunicação entre uma fonte de informação e o seu destino. Por exemplo, uma corrente de entrada permite-lhe ler dados em uma fonte, e uma corrente de produção permite-lhe escrever dados a um destino.

Os métodos de uso geral que podem ler em qualquer fonte aceitam que um argumento de corrente especifica aquela fonte; os métodos de uso geral da escrita aceitam que uma corrente especifica o destino. Os processadores arbitrais de dados comumente têm dois argumentos de corrente. Leem desde o princípio, processam os dados e escrevem os resultados ao segundo. Estes processadores não têm nenhuma ideia da fonte ou do destino dos dados que processam. As fontes e os destinos podem variar largamente: de dois buffers de memória no mesmo computador local, ao ELFO (extremamente baixa frequência) as transmissões a e de um submarino no mar, às correntes de dados em tempo real de uma NASA tentam no espaço profundo.

Separando a consumação, processamento ou produção de dados das fontes e os destinos daqueles dados, pode misturar e combinar com qualquer combinação deles à vontade como escreve o seu programa. No futuro, quando novo, as formas anteriormente não existentes de fonte ou destino (ou consumidor, processador ou produtor) aparecem, podem usar-se dentro da mesma armação, sem modificações das suas classes. Além disso, as novas abstrações de corrente, apoiando níveis mais altos da interpretação em cima dos bytes, podem escrever-se completamente independentemente dos mecanismos de transporte subjacentes dos próprios bytes.

O pacote de java.io

Todas as classes sobre as quais aprenderá hoje são parte do pacote java.io. Para usar alguma destas classes nos seus próprios programas, precisará de importar cada classe individual ou importar o pacote de java.io inteiro, como isto:

import java.io.InputStream;
import java.io.FilteredInputStream;
import java.io.FileOutputStream;

import java.io.*;

Declara-se que todos os métodos que explorará hoje lancem IOException s. Esta nova subclasse de Exception conceptualmente personifica todos os erros de entrada-saída possíveis que poderiam ocorrer usando correntes. Várias subclasses dele definem alguns, exceções mais específicas que podem lançar-se também. Por agora, é bastante saber que deve catch um IOException, ou estar em um método que pode "passar ao longo dele", ser um usuário bem-comportado de correntes.

As fundações desta armação de corrente na hierarquia de classe de Java são as duas classes abstratas, InputStream e OutputStream. Herdar destas classes é uma cornucópia virtual de subclasses categorizadas, demonstrando a ampla variação de correntes no sistema, mas também demonstrando uma hierarquia extremamente bem projetada de relações entre estas correntes uma que bem vale a pena aprender de. Vamos começar com os pais, InputStream e OutputStream, e vamos trabalhar logo o nosso caminho abaixo esta árvore fechada.

Correntes de entrada

As correntes de entrada são correntes que lhe permitem ler dados em uma fonte. Estes incluem a classe abstrata InputStream de raiz, correntes filtradas, armazenou em buffer correntes e correntes que leem em arquivos, cadeias e tabelas de byte.

A classe abstrata InputStream

InputStream é uma classe abstrata que define os caminhos fundamentais dos quais um destino (consumidor) lê uma corrente de bytes de alguma fonte. A identidade da fonte, e maneira da criação e transporte dos bytes, é inaplicável. Usando uma corrente de entrada, é o destino daqueles bytes, e isto é tudo que tem de saber.

Observar
Todas as correntes de entrada descem de InputStream. Toda a ação em comum os poucos métodos descreve-se nesta seção. Assim, as correntes usadas nestes exemplos podem ser alguma das correntes de entrada mais complexas descritas nas seguintes poucas seções.

read()

O método mais importante ao consumidor de uma corrente de entrada é aquele que lê bytes na fonte. Este método, read(), vem para muitos sabores, e cada um demonstra-se em um exemplo na lição de hoje.

Cada um destes métodos de read() define-se para "bloquear" (esperam) até que toda a entrada solicitada fique disponível. Não se incomode com esta limitação; por causa do multifilamento, pode fazer tantas outras coisas como você gosta enquanto este fio espera pela entrada. De fato, é um idioma comum para destinar um fio a cada corrente da entrada (e para cada corrente da produção) que é sozinho responsável por ler nele (ou escrever-lhe). Estes fios de entrada então poderiam "transmitir" a informação a outros fios do processamento. Isto naturalmente fica sobreposto ao tempo de entrada-saída do seu programa com o seu computar o tempo.

Aqui está a primeira forma de read():

InputStream  s      = getAnInputStreamFromSomewhere();
byte[]       buffer = new byte[1024];   // any size will do

if (s.read(buffer) != buffer.length)
    System.out.println("I got less than I expected.");

Observar
Aqui e durante o resto da lição de hoje, suponha que um import java.io.* apareça antes de todos os exemplos ou que você mentalmente prefixo todas as referências para classes de java.io com o prefixo java.io.

Esta forma de read() tenta encher o buffer inteiro dado. Se não puder (normalmente devido a conseguir o fim da corrente de entrada), devolve o número real de bytes que se leram no buffer. Depois disto, mais longe chama ao regresso de read() -1, indicando que é no fim da corrente. Observe que a afirmação de if ainda trabalha até neste caso, porque -1 != 1024 (isto corresponde a uma corrente de entrada sem bytes nele em absoluto).

Observar
Não se esqueça de que, diferentemente de em C, o caso de -1 em Java não se usa para indicar um erro. Qualquer erro de entrada-saída lança exemplos de IOException (que ainda não pega). Aprendeu no Dia 17, "Exceções", que todos os usos de valores eminentes podem substituir-se pelo uso de exceções, e portanto devem. O -1 no exemplo último é um bocado de um anacronismo histórico. Verá logo uma melhor aproximação da indicação do fim da corrente usando a classe DataInputStream.

Também pode ler em uma "fatia" do seu buffer especificando a compensação no buffer e o comprimento desejado, como argumentos a read():

s.read(buffer, 100, 300);

Este exemplo tenta preencher bytes 100 para 399 e comporta-se de outra maneira exatamente o mesmo como o método de read() prévio.

Finalmente, pode ler em bytes um após outro:

InputStream  s = getAnInputStreamFromSomewhere();
byte         b;
int          byteOrMinus1;

while ((byteOrMinus1 = s.read()) != -1) {
     b = (byte) byteOrMinus1;
     . . .    // process the byte b
}
. . .    // reached end of stream

Observar
Por causa da natureza da promoção de número inteiro em Java em geral, e porque neste caso o método de read() devolve um int, usando o tipo de byte no seu código pode ser pouca frustração. Vai se encontrar que constantemente tem necessidade de lançar explicitamente o resultado de expressões aritméticas, ou de valores de retorno de int, atrás ao seu tamanho. Como read() realmente deve estar devolvendo um byte neste caso, sentimo-nos justificados em declaração e utilização dele como tal (apesar da dor) - faz o tamanho dos dados que se leem mais claro. Em casos onde sente que a variedade de uma variável se limita naturalmente a um byte (ou um short) em vez de um int, por favor não apresse-se para declará-lo aquele caminho e pagar o pequeno preço necessário para ganhar a claridade acrescentada. A propósito, muito código de biblioteca de classe de Java simplesmente guarda o resultado de read() em um int.

skip()

E se quer omitir sobre alguns bytes em uma corrente ou começar a ler uma corrente no outro do que o seu começo? Um método semelhante a read() acerta em cheio:

if (s.skip(1024) != 1024)
    System.out.println("I skipped less than I expected.");

Este exemplo omite sobre seguintes 1.024 bytes na corrente de entrada. Contudo, a implementação de skip() em InputStream pode omitir menos bytes do que o argumento dado, e portanto devolve um número inteiro longo que representa o número de bytes de fato omitiu. Neste exemplo, por isso, uma mensagem imprime-se se o número real de bytes omitiu é menos de 1.024.

Observar
A documentação API de skip() na classe de InputStream diz que skip() se comporta este caminho por "várias razões". As subclasses de InputStream devem ignorar esta implementação à revelia de skip() se quiserem tratar omitir mais propriamente.

available()

Se por alguma razão você gostaria de saber quantos bytes estão na corrente agora mesmo, pode perguntar o seguinte:

if (s.available() < 1024)
    System.out.println("Too little is available right now.");

Isto diz-lhe o número de bytes que pode ler sem bloqueio. Por causa da natureza abstrata da fonte destes bytes, as correntes podem ou podem não ser capazes de dizer-lhe uma resposta razoável a esta pergunta. Por exemplo, algumas correntes sempre devolvem 0. A menos que use subclasses específicas de InputStream que sabe fornecem uma resposta razoável a esta pergunta, não é uma boa ideia de confiar neste método. Lembre-se de que o multifilamento elimina muitos dos problemas associados com o bloqueio esperando por uma corrente para encher-se novamente. Assim, uma das bases lógicas mais fortes do uso de available() vai-se.

mark() e reset()

Algumas correntes apoiam a noção de marcar uma posição na corrente e reinicialização logo posterior da corrente àquela posição para reler os bytes lá. Claramente, a corrente teria de "lembrar-se" de todos aqueles bytes, então há uma limitação da que distância à parte em uma corrente a marca e a sua recomposição subsequente podem ocorrer. Também há um método que pergunta se a corrente apoia a noção da marcação em absoluto. Aqui está um exemplo:

InputStream  s = getAnInputStreamFromSomewhere();

if (s.markSupported()) {    // does s support the notion?
    . . .        // read the stream for a while
    s.mark(1024);
    . . .        // read less than 1024 more bytes
    s.reset();
    . . .        // we can now re-read those bytes
} else {
    . . .                   // no, perform some alternative
}

Marcando uma corrente, especifica o número máximo de bytes que pretende permitir passar antes de reinicializá-lo. Isto permite à corrente limitar o tamanho do seu byte "memória". Se este número de bytes for por e ainda não usou reset(), a marca fica inválida, e tentando usar reset() lançará uma exceção.

Marcar e reinicializar uma corrente são os mais valiosos quando tenta identificar o tipo da corrente (ou a seguinte parte da corrente), mas fazer assim, deve consumir uma parte significante dele no processo. Muitas vezes, isto é porque tem vários analisadores gramaticais de caixa preta que pode entregar a corrente, mas consumirão alguns (desconhecido para você) número de bytes antes de decidir-se sobre se a corrente é do seu tipo. Estabeleça um grande tamanho do limite em mark() e deixe cada analisador gramatical não correr até ele lança um erro ou conclui uma análise bem sucedida. Se um erro se lançar, use reset() e tente o seguinte analisador gramatical.

close()

Como não sabe que recursos uma corrente aberta representa, nem como tratar com eles propriamente quando se termina lendo a corrente, deve fechar (normalmente) explicitamente uma corrente para que possa lançar estes recursos. Naturalmente, a coleção de lixo e um método de finalização podem fazer isto para você, mas e se tiver de reabrir aquela corrente ou aqueles recursos antes que se tenham libertado por este processo assíncrono? Na melhor das hipóteses, isto é aborrecido ou confuso; na pior das hipóteses, introduz um inesperado, obscuro, e difícil de seguir a pista abaixo do defeito. Como interage com o mundo exterior de recursos externos, está mais seguro ser explícito sobre quando se termina usando-os:

InputStream  s = alwaysMakesANewInputStream();

try {
    . . .     // use s to your heart's content
} finally {
    s.close();
}

Acostume-se a este idioma (usando finally); é um modo útil de estar seguro que algo (como encerramento da corrente) sempre se faz. Naturalmente, está supondo que a corrente sempre se crie com sucesso. Se isto for não sempre o caso, e null devolve-se às vezes em vez disso, aqui está o modo correto de estar seguro:

InputStream  s = tryToMakeANewInputStream();

if (s != null) {
    try {
        . . .
    } finally {
        s.close();
    }
}

ByteArrayInputStream

A "inversão" de alguns exemplos prévios deveria criar uma corrente de entrada de uma tabela de bytes. Isto é exatamente o que ByteArrayInputStream faz:

byte[]  buffer = new byte[1024];

fillWithUsefulData(buffer);

InputStream  s = new ByteArrayInputStream(buffer);

Os leitores da nova corrente s veem uma corrente de 1.024 bytes de longitude, contendo os bytes na tabela buffer. Tão como read() tem uma forma que toma uma compensação e um comprimento, então faz o construtor desta classe:

InputStream  s = new ByteArrayInputStream(buffer, 100, 300);

Aqui a corrente é 300 bytes de longitude e compõe-se de bytes 100-399 da tabela buffer.

Observar
Finalmente, viu os seus primeiros exemplos da criação de uma corrente. Estas novas correntes anexam-se à mais simples de todas as fontes possíveis de dados: uma tabela de bytes na memória do computador local.

ByteArrayInputStream s simplesmente implementa o conjunto padrão de métodos que todas as correntes de entrada fazem. Aqui, contudo, o método de available() tem um emprego especialmente simples - devolve 1024 e 300, respectivamente, para os dois exemplos de ByteArrayInputStream que criou anteriormente, porque sabe exatamente quantos bytes estão disponíveis. Finalmente, chamar reset() em um ByteArrayInputStream reinicializa-o com o começo da corrente (buffer), não importa onde a marca se estabelece.

FileInputStream

Um dos usos mais comuns de correntes, e historicamente o mais primeiro, devem anexá-los a arquivos no sistema de arquivos. Aqui, por exemplo, é a criação de tal corrente de entrada em um sistema UNIX:

InputStream  s = new FileInputStream("/some/path/and/fileName");

Aviso
Applets que tenta abrir-se, leia ou escreva que as correntes baseadas em arquivos no sistema de arquivos farão normalmente que a exceções de segurança se lancem do browser. Se estiver desenvolvendo applets, não será capaz de depender de arquivos em absoluto, e terá de usar o seu servidor para manter a informação compartilhada. (Os programas Java autônomos não têm nenhum destes problemas, naturalmente.)

Também pode criar a corrente de um descritor de arquivo anteriormente aberto (um exemplo da classe de FileDescriptor). Normalmente, adquire descritores de arquivo usando o método de getFD() em FileInputStream ou classes de FileOutputStream, portanto, por exemplo, pode usar o mesmo descritor de arquivo para abrir um arquivo da leitura e logo reabri-lo para a escrita:

FileDescriptor       fd = someFileStream.getFD();
InputStream  s  = new FileInputStream(fd);

Em qualquer caso, porque é baseado em um real (comprimento finito) o arquivo, a corrente de entrada criada pode implementar available() precisamente e pode omitir como um campeão (como ByteArrayInputStream pode, a propósito). Além disso, FileInputStream sabe muitos outros truques:

FileInputStream  aFIS = new FileInputStream("aFileName");

FileDescriptor  myFD = aFIS.getFD(); // get a file descriptor

 aFIS.finalize();   // will call close() when automatically called by GC

Ponta
Para chamar estes novos métodos, deve declarar que a variável de corrente aFIS seja do tipo FileInputStream, porque a planície InputStream s não sabe sobre eles.

O primeiro é óbvio: getFD() devolve o descritor de arquivo do arquivo no qual a corrente se baseia. O segundo, entretanto, é um atalho interessante que lhe permite criar FileInputStream s sem incomodar-se com o encerramento deles depois. a implementação de FileInputStream de finalize(), método de protected, fecha a corrente. Diferentemente de na chamada elaborada em comentários, quase nunca pode nem deve chamar um método de finalize() diretamente. O coletor de lixo chama-o depois notar que a corrente não está já no uso, mas antes de destruir de fato a corrente. Assim, pode ir alegremente ao longo da utilização da corrente, nunca o fechando, e todos serão bem. O sistema cuida do encerramento dele (consequentemente).

Pode partir com isto porque as correntes baseadas em arquivos amarram muito poucos recursos, e estes recursos não podem reutilizar-se acidentalmente antes da coleção de lixo (estas foram as coisas preocupadas com na discussão prévia da finalização e close()). Naturalmente, se também escrevia ao arquivo, teria de mais ter cuidado. (Reabrir o arquivo demasiado logo depois da escrita poderia fazê-lo aparecer em um estado inconsistente porque o finalize() - e assim o close() - poderiam não ter acontecido ainda.) Somente porque não tem de fechar a corrente não significa que não poderia querer fazer então de qualquer maneira. Para a claridade, ou se não sabe precisamente que tipo de InputStream se entregou, poderia decidir chamar close() você mesmo.

FilterInputStream

Esta classe "abstrata" simplesmente fornece uma "abertura na parede" para todos os métodos padrão de InputStream. (É "abstrato", em citações, porque não é tecnicamente uma classe de abstract; pode criar exemplos dele. Na maioria dos casos, contudo, usará uma das subclasses mais úteis de FilterInputStream em vez de próprio FilterInputStream.) FilterInputStream mantém dentro de si mesmo outra corrente, por definição uma além disso "abaixo" a cadeia de filtros, a que ele adiante todas as chamadas de método. Não implementa nada de novo mas permite-se aninhar-se:

InputStream        s  = getAnInputStreamFromSomewhere();
FilterInputStream  s1 = new FilterInputStream(s);
FilterInputStream  s2 = new FilterInputStream(s1);
FilterInputStream  s3 = new FilterInputStream(s2);

... s3.read() ...

Sempre que um lido se execute na corrente filtrada s3, passa ao longo do pedido a s2, então s2 faz o mesmo a s1, e finalmente a s pedem para fornecer os bytes. As subclasses de FilterInputStream farão, naturalmente, algum processamento não-trivial dos bytes como fluem correndo. A forma bastante verbosa "do encadeamento" no exemplo prévio pode fazer-se mais elegante:

s3 = new FilterInputStream(new FilterInputStream(new FilterInputStream(s)));

Deve usar este idioma no seu código sempre que possa. Claramente exprime o aninhamento de filtros encadeados e pode analisar-se facilmente e "leem em voz alta" começando na corrente íntima s e lendo externo - cada corrente de filtro que aplica àquela dentro de - até que consiga a corrente externa s3.

Agora vamos examinar cada uma das subclasses de FilterInputStream à sua vez.

BufferedInputStream

Isto é uma das mais valiosas de todas as correntes. Implementa o complemento cheio de métodos de InputStream, mas faz assim usando uma tabela armazenada em buffer de bytes que substitui como um esconderijo sob a futura leitura. Isto separa a tarifa e o tamanho dos "pedaços" que lê nos tamanhos de bloco mais regulares, maiores nos quais as correntes se leem o mais eficientemente (em, por exemplo, dispositivos periféricos, arquivos no sistema de arquivos ou a rede). Também permite a correntes inteligentes ler adiante quando esperam que quererá mais dados logo.

Como armazenar em buffer de BufferedInputStream é tão valioso, e também é a única classe para tratar mark() e reset() propriamente, poderia lamentar que cada corrente de entrada não possa compartilhar de qualquer maneira as suas capacidades valiosas. Normalmente, porque aquelas classes de corrente não os implementam, teria azar. Afortunadamente, já viu um caminho que as correntes de filtro podem enrolar elas mesmas "em volta" de outras correntes. Suponha que você gostaria de um FileInputStream armazenado em buffer que pode tratar a marcação e a reinicialização corretamente. E voilà:

InputStream  s = new BufferedInputStream(new FileInputStream("foo"));

Tem uma corrente de entrada armazenada em buffer baseada no arquivo foo que pode usar mark() e reset().

Agora pode começar a ver o poder de correntes de aninhamento. Qualquer capacidade fornecida por um filtro introduziu a corrente (ou corrente de produção, como verá logo) pode usar-se por qualquer outra corrente básica via o aninhamento. Naturalmente, qualquer combinação destas capacidades, e em qualquer ordem, pode realizar-se como facilmente pelo aninhamento as próprias correntes de filtro.

DataInputStream

Todos os métodos que os exemplos desta classe entendem definem-se em uma interface separada, que tanto DataInputStream como RandomAccessFile (outra classe em java.io) implementam. Esta interface é bastante de uso geral que poderia querer usá-la você mesmo nas classes que cria. Chama-se DataInput.

A interface de DataInput

Quando começa a usar correntes a qualquer grau, descobrirá rapidamente que as correntes de byte não são um formato realmente útil no qual forçar todos os dados. Especialmente, os tipos primitivos da língua de Java personificam um modo bastante bonito de olhar para dados, mas com as correntes tem definido até aqui neste livro, não pode ler dados destes tipos. A interface de DataInput especifica um jogo de nível mais alto de métodos que, quando usado tanto para leitura como para escrita, podem apoiar uma corrente mais complexa, datilografada de dados. Aqui estão os métodos que esta interface define:

void  readFully(byte[]  buffer)                           throws IOException;
void  readFully(byte[]  buffer, int  offset, int  length) throws IOException;
int   skipBytes(int n)                                    throws IOException;

boolean  readBoolean()       throws IOException;
byte     readByte()          throws IOException;
int      readUnsignedByte()  throws IOException;
short    readShort()         throws IOException;
int      readUnsignedShort() throws IOException;
char     readChar()          throws IOException;
int      readInt()           throws IOException;
long     readLong()          throws IOException;
float    readFloat()         throws IOException;
double   readDouble()        throws IOException;

String   readLine()          throws IOException;
String   readUTF()           throws IOException;

Três primeiros métodos são simplesmente novos nomes de skip() e as duas formas de read() que viu anteriormente. Cada um de 10 seguintes métodos lê em um tipo primitivo ou o seu colega não assinado (útil para usar cada bit eficientemente em uma corrente binária). Estes últimos métodos devem devolver um número inteiro de um mais largo tamanho do que poderia pensar; porque os números inteiros se assinam em Java, o valor não assinado não se ajusta em nada mais pequeno. Dois métodos finais leem um newline ('\r', '\n' ou "\r\n") série terminada de carateres da corrente - o primeiro em ASCII e o segundo em Unicode.

Agora que sabe o que a interface a que os instrumentos de DataInputStream parecem, nos deixa vê-lo na ação:

DataInputStream  s = new DataInputStream(myRecordInputStream());

long  size = s.readLong();    // the number of items in the stream

while (size-- > 0) {
    if (s.readBoolean()) {    // should I process this item?
        int     anInteger     = s.readInt();
        int     magicBitFlags = s.readUnsignedShort();
        double  aDouble       = s.readDouble();

        if ((magicBitFlags & 0100000) != 0) {
            . . .    // high bit set, do something special
        }
        . . .    // process anInteger and aDouble
    }
}

Como a classe implementa uma interface de todos os seus métodos, também pode usar a seguinte interface:

DataInput  d = new DataInputStream(new FileInputStream("anything"));
String     line;

while ((line = d.readLine()) != null) {
    . . .     // process the line
}

EOFException

Um ponto final sobre a maioria dos métodos de DataInputStream: Quando o fim da corrente se consegue, os métodos lançam um EOFException. Isto é tremendamente útil e, de fato, permite-lhe reescrever todos os usos kludgey de -1 que viu antes hoje de uma maneira muito mais bonita:

DataInputStream  s = new DataInputStream(getAnInputStreamFromSomewhere());

try {
    while (true) {
        byte  b = (byte) s.readByte();
        . . .    // process the byte b
    }
} catch (EOFException e) {
    . . .    // reached end of stream
} finally {
  s.close();
}

Isto trabalha ainda bem para todos exceto dois últimos dos métodos de read de DataInputStream.

Aviso
skipBytes() não faz nada em absoluto no fim da corrente, readLine() devolve null, e readUTF() podia throw um UTFDataFormatException, se nota o problema em absoluto.

LineNumberInputStream

Em um editor ou um depurador, a numeração de linha é crucial. Para acrescentar esta capacidade valiosa aos seus programas, use a corrente de filtro LineNumberInputStream, que guarda a pista de números de linha como a sua corrente "flui por" ele. É até bastante inteligente para lembrar-se de um número de linha e depois restaurá-lo, durante um mark() e reset(). Poderia usar esta classe como se segue:

LineNumberInputStream  aLNIS;
aLNIS = new LineNumberInputStream(new FileInputStream("source"));

DataInputStream  s = new DataInputStream(aLNIS);
String           line;

while ((line = s.readLine()) != null) {
    . . .    // process the line
    System.out.println("Did line number: " + aLNIS.getLineNumber());
}

Aqui, duas correntes de filtro aninham-se em volta do FileInputStream que de fato fornece os dados - o primeiro para ler linhas um após outro e o segundo para guardar a pista dos números de linha destas linhas quando vão por. Deve denominar explicitamente a corrente de filtro intermediária, aLNIS, porque se não fez, não pode chamar getLineNumber() depois. Observe que se inverter a ordem das correntes aninhadas, que leem em DataInputStream não faz que a LineNumberInputStream "veja" as linhas.

Deve pôr qualquer corrente de filtro que atua como "monitores" no meio da cadeia e "puxar" os dados da corrente de filtro externa para que os dados passem por cada um dos monitores à sua vez. De mesmo modo, armazenar em buffer deve ocorrer o mais longe dentro da cadeia possível, porque a corrente armazenada em buffer não será capaz de fazer o seu emprego propriamente a menos que a maioria das correntes que precisam de armazenar em buffer venham depois dele para o fluxo. Por exemplo, aqui está uma ordem boba:

new BufferedInputStream(new LineNumberInputStream(
            _new DataInputStream(new FileInputStream("foo"));

e aqui está uma ordem muito melhor:

new DataInputStream(new LineNumberInputStream(
            _new BufferedInputStream(new FileInputStream("foo"));

LineNumberInputStream s também pode dizer-se setl1ineNumber(), para aquelas poucas vezes quando sabe mais do que eles.

PushbackInputStream

O PushbackInputStream de classe de corrente de filtro usa-se comumente em analisadores gramaticais, para "repelir" um caráter único na entrada (depois lê-lo) tentando determinar que fazer a versão depois-a simplificada do mark() e utilidade de reset() sobre a qual aprendeu antes. A sua única adição ao conjunto padrão de métodos de InputStream é unread(), que, como poderia adivinhar, finge que nunca leu o byte passou em como o seu argumento, e logo devolve aquele byte como o valor de retorno do seguinte read().

A listagem 19.1 mostra uma implementação simples de readLine() usando esta classe:


A listagem 19.1. Um leitor de linha simples.
 1:import java.io;
 2:
 3:public class  SimpleLineReader {
 4:    private FilterInputStream  s;
 5:
 6:    public  SimpleLineReader(InputStream  anIS) {
 7:        s = new DataInputStream(anIS);
 8:    }
 9:
10:    . . .    // other read() methods using stream s
11:
12:    public String  readLine() throws IOException {
13:        char[]  buffer = new char[100];
14:        int     offset = 0;
15:        byte    thisByte;
16:
17:        try {
18:loop:        while (offset < buffer.length) {
19:                switch (thisByte = (byte) s.read()) {
20:                    case '\n':
21:                        break loop;
22:                    case '\r':
23:                        byte  nextByte = (byte) s.read();
24:
25:                        if (nextByte != '\n') {
26:                            if (!(s instanceof PushbackInputStream)) {
27:                                s = new PushbackInputStream(s);
28:                            }
29:                            ((PushbackInputStream) s).unread(nextByte);
30:                        }
31:                        break loop;
32:                    default:
33:                        buffer[offset++] = (char) thisByte;
34:                        break;
35:                }
36:            }
37:        } catch (EOFException e) {
38:            if (offset == 0)
39:                return null;
40:        }
41:          return String.copyValueOf(buffer, 0, offset);
42:     }
43:}

Este exemplo demonstra coisas numerosas. Para fins deste exemplo, o método de readLine() restringe-se à leitura de 100 primeiros carateres da linha. Neste aspecto, demonstra como não escrever um processador de linha de uso geral (deve ser capaz de ler uma linha de qualquer tamanho). Este exemplo realmente, contudo, mostra-lhe como libertar-se de um laço exterior (usando a etiqueta loop na linha 18 e as afirmações de break em linhas 21 e 31), e como produzir um String de uma tabela de carateres (neste caso, de uma "fatia" da tabela de carateres). Este exemplo também inclui usos padrão de read() de InputStream para ler bytes um após outro, e de determinar o fim da corrente cercando-o em um DataInputStream e pegando EOFException.

Um dos aspectos mais excepcionais do exemplo é o modo que PushbackInputStream se usa. Para estar seguro que '\n' se ignora depois de '\r', tem de "olhar adiante" um caráter; mas se não é um '\n', deve repelir aquele caráter. Olhe para as linhas 26 para 29 como se não soubesse muito sobre a corrente s. A técnica geral usada é instrutiva. Em primeiro lugar, vê se s já é um exemplo de uma espécie de PushbackInputStream. Nesse caso simplesmente pode usá-lo. Se não, cercar a corrente atual (tudo o que seja) dentro de um novo PushbackInputStream e use esta nova corrente. Agora, vamos pular atrás no contexto do exemplo.

A linha 29 depois que a afirmação de if na linha 26 quer chamar o método unread(). O problema consiste em que s tem compilar o tipo vezes de FilterInputStream, e assim não entende aquele método. Três linhas prévias (26) garantiram, contudo, que o tipo em tempo de execução da corrente em s é PushbackInputStream, portanto pode lançá-lo seguramente àquele tipo e logo seguramente chamar unread().

Observar
Este exemplo fez-se de um modo excepcional com objetivos de manifestação. Simplesmente pode ter declarado uma variável de PushbackInputStream e sempre ter cercado o DataInputStream nele. (De modo inverso, o construtor de SimpleLineReader pode ter verificado se o seu argumento já foi da classe direita, o modo que PushbackInputStream fez, antes de criar um novo DataInputStream.) A coisa interessante sobre esta aproximação de enrolar uma classe só quando necessário é que trabalha para qualquer InputStream que o entrega, e faz o trabalho adicional só se precisar. Ambos destes são bons princípios de desenho gerais.

Todas as subclasses de FilterInputStream descreveram-se agora. É tempo de voltar às subclasses diretas de InputStream.

PipedInputStream

Esta classe, junto com sua classe de irmão PipedOutputStream, é coberta depois hoje (têm de entender-se e demonstrar-se em conjunto). Por agora, tudo que tem de saber é que em conjunto criam um canal de comunicação simples, de duas vias entre fios.

SequenceInputStream

Suponha que tem duas correntes separadas e você gostaria de fazer uma corrente composta que se compõe de uma corrente seguida do outro (como acrescentar dois String s em conjunto). Isto é exatamente para que SequenceInputStream se criou:

InputStream  s1 = new FileInputStream("theFirstPart");
InputStream  s2 = new FileInputStream("theRest");

InputStream  s  = new SequenceInputStream(s1, s2);

... s.read() ...   // reads from each stream in turn

Pode ter "falso" este exemplo lendo cada arquivo na volta - mas e se teve de entregar à corrente composta s a algum outro método que esperava só um InputStream único? Aqui está um exemplo (usando s) que números da linha dois arquivos prévios com um esquema de numeração comum:

LineNumberInputStream  aLNIS = new LineNumberInputStream(s);

... aLNIS.getLineNumber() ...

Observar
Esticar em conjunto corre este caminho é especialmente útil quando as correntes são de comprimento desconhecido e origem e somente se entregaram você por alguém mais.

E se quer esticar em conjunto mais de duas correntes? Pode tentar o seguinte:

Vector  v = new Vector();
. . .   // set up all the streams and add each to the Vector
InputStream  s1 = new SequenceInputStream(v.elementAt(0), v.elementAt(1));
InputStream  s2 = new SequenceInputStream(s1, v.elementAt(2));
InputStream  s3 = new SequenceInputStream(s2, v.elementAt(3));
. . .

Observar
Um Vector é uma tabela growable de objetos que podem encher-se, referir-se (com elementAt()) e enumerar-se.

Contudo, é muito mais fácil usar um construtor diferente que SequenceInputStream provê:

InputStream  s  = new SequenceInputStream(v.elements());

Este construtor toma um argumento - um objeto do tipo Enumeration (neste exemplo, adquirimos aquele objeto usando o método de elements() DE Vector). O objeto de SequenceInputStream resultante contém todas as correntes que quer combinar e devolve uma corrente única que lê os dados do princípio ao fim de cada um à sua vez.

StringBufferInputStream

StringBufferInputStream parece-se exatamente com ByteArrayInputStream, mas em vez de ser baseado em uma tabela de byte, é baseado em uma tabela de carateres (um String):

String       buffer = "Now is the time for all good men to come...";
InputStream  s      = new StringBufferInputStream(buffer);

Todos os comentários que se fizeram sobre ByteArrayInputStream aplicam-se aqui também.

Observar
StringBufferInputStream é um bocado de um erro de nome porque esta corrente de entrada é de fato baseada em um String. Realmente deve chamar-se StringInputStream.

Correntes de produção

Uma corrente de produção é o reverso de uma corrente de entrada; ao passo que com uma corrente de entrada lê dados na corrente, com correntes de produção escreve dados à corrente. A maioria das subclasses de InputStream que já viu têm as suas classes de irmão de OutputStream equivalentes. Se um InputStream executar certa operação, o irmão OutputStream executa a operação inversa. Verá mais do que isto significa logo.

A classe abstrata OutputStream

OutputStream é a classe abstrata que define os caminhos fundamentais dos quais uma fonte (produtor) escreve uma corrente de bytes a algum destino. A identidade do destino, e maneira do transporte e armazenamento dos bytes, é inaplicável. Usando uma corrente de produção, é a fonte daqueles bytes, e isto é tudo que tem de saber.

write()

O método mais importante ao produtor de uma corrente de produção é aquele que escreve bytes ao destino. Este método, write(), vem para muitos sabores, cada um demonstrado nos seguintes exemplos:

Observar
Cada um destes métodos de write() define-se para bloquear até que toda a produção solicitada se tenha escrito. Não precisa de incomodar-se com esta limitação - veem a nota abaixo do método de read() DE InputStream se não se lembrar porque.

OutputStream  s      = getAnOutputStreamFromSomewhere();
byte[]        buffer = new byte[1024];    // any size will do

fillInData(buffer);    // the data we want to output
s.write(buffer);

Também pode escrever uma "fatia" do seu buffer especificando a compensação no buffer e o comprimento desejado, como argumentos a write():

s.write(buffer, 100, 300);

Este exemplo escreve bytes 100 para 399 e comporta-se de outra maneira exatamente o mesmo como o método de write() prévio.

Finalmente, pode escrever bytes um após outro:

while (thereAreMoreBytesToOutput()) {
    byte  b = getNextByteForOutput();

    s.write(b);
}

flush()

Como não sabe a que uma corrente de produção se une, poderia dever "corar" a sua produção por alguns armazenou em buffer o esconderijo para conseguir que ela se escreva (em uma maneira oportuna, ou em absoluto). a versão de OutputStream deste método não faz nada, mas espera-se que as subclasses que necessitam a inundação (por exemplo, BufferedOutputStream e PrintStream) ignorarão esta versão para fazer algo não-trivial.

close()

Como para um InputStream, deve fechar (normalmente) explicitamente um OutputStream para que possa lançar qualquer recurso que pode ter reservado da sua parte. (Mesmo assim as notas e os exemplos do método de close() DE InputStream aplicam-se aqui, com o prefixo In substituído em todo lugar por Out.)

Todas as correntes de produção descem da classe abstrata OutputStream. Toda a ação os poucos métodos prévios em comum.

ByteArrayOutputStream

A inversão de ByteArrayInputStream, que cria uma corrente de entrada de uma tabela de bytes, é ByteArrayOutputStream, que dirige uma corrente de produção em uma tabela de bytes:

OutputStream  s = new ByteArrayOutputStream();

s.write(123);
. . .

O tamanho da (interna) tabela de byte cresce como necessário de guardar uma corrente de qualquer comprimento. Pode fornecer uma capacidade inicial como uma ajuda à classe, se você gostar:

OutputStream  s = new ByteArrayOutputStream(1024 * 1024);  // 1 Megabyte

Observar
Acaba de ver os seus primeiros exemplos da criação de uma corrente de produção. Estas novas correntes anexaram-se ao mais simples de todos os destinos possíveis de dados, uma tabela de bytes na memória do computador local.

Uma vez que o objeto de ByteArrayOutputStream, guardado no s variável, "encheu-se", pode ser produção a outra corrente de produção:

OutputStream           anotherOutputStream = getTheOtherOutputStream();
ByteArrayOutputStream  s = new ByteArrayOutputStream();

fillWithUsefulData(s);
s.writeTo(anotherOutputStream);

Também pode extrair-se como uma tabela de byte ou converter-se em um String:

byte[]  buffer              = s.toByteArray();
String  bufferString        = s.toString();
String  bufferUnicodeString = s.toString(upperByteValue);

Observar
O método último permite-lhe "falsificar" Unicode carateres (de 16 bits) preenchendo os seus bytes mais baixos com ASCII e logo especificando um byte superior comum (normalmente 0) para criar um resultado de String Unicode.

ByteArrayOutputStream s tem dois métodos de serviço: Cada um simplesmente devolve o número atual de bytes guardados na tabela de byte interna e outras recomposições a tabela para que a corrente possa reescrever-se do começo:

int  sizeOfMyByteArray = s.size();

s.reset();     // s.size() would now return 0
s.write(123);
. . .

FileOutputStream

Um dos usos mais comuns de correntes deve anexá-los a arquivos no sistema de arquivos. Aqui, por exemplo, é a criação de tal corrente de produção em um sistema UNIX:

OutputStream  s = new FileOutputStream("/some/path/and/fileName");

Aviso
Applets que tenta abrir-se, leia ou escreva que as correntes baseadas em arquivos no sistema de arquivos causarão violações de segurança. Ver a nota abaixo de FileInputStream de mais detalhes.

Como com FileInputStream, também pode criar a corrente de um descritor de arquivo anteriormente aberto:

FileDescriptor           fd = someFileStream.getFD();
OutputStream  s  = new FileOutputStream(fd);

FileOutputStream é a inversão de FileInputStream, e sabe os mesmos truques:

FileOutputStream  aFOS = new FileOutputStream("aFileName");

FileDescriptor  myFD = aFOS.getFD(); // get a file descriptor

aFOS.finalize();  // will call close() when automatically called by GC

Observar
Para chamar os novos métodos, deve declarar que a variável de corrente aFOS seja do tipo FileOutputStream, porque a planície OutputStream s não sabe sobre eles.

O primeiro é óbvio: getFD() simplesmente devolve o descritor de arquivo do arquivo no qual a corrente se baseia. O segundo, comentou, a chamada inventada a finalize() está lá para lembrar-lhe que não deveria incomodar-se com o encerramento deste tipo da corrente - faz-se para você automaticamente.

FilterOutputStream

Esta classe abstrata simplesmente fornece uma "abertura na parede" para todos os métodos padrão de OutputStream. Mantém dentro de si mesmo outra corrente, por definição uma além disso "abaixo" a cadeia de filtros, a que ele adiante todas as chamadas de método. Não implementa nada de novo mas permite-se aninhar-se:

OutputStream        s  = getAnOutputStreamFromSomewhere();
FilterOutputStream  s1 = new FilterOutputStream(s);
FilterOutputStream  s2 = new FilterOutputStream(s1);
FilterOutputStream  s3 = new FilterOutputStream(s2);

... s3.write(123) ...

Sempre que escrever se execute na corrente filtrada s3, passa ao longo do pedido a s2. Então s2 faz o mesmo a s1, e finalmente perguntam a s à produção os bytes. As subclasses de FilterOutputStream, naturalmente, fazem algum processamento não-trivial dos bytes como fluem correndo. Esta cadeia pode aninhar-se justamente - ver sua classe de irmão, FilterInputStream, para mais.

Agora vamos examinar cada uma das subclasses de FilterOutputStream à sua vez.

BufferedOutputStream

BufferedOutputStream é uma das mais valiosas de todas as correntes. Tudo que faz é implementar o complemento cheio de métodos de OutputStream, mas faz assim usando uma tabela armazenada em buffer de bytes que substitui como um esconderijo sob a escrita. Isto separa a tarifa e o tamanho dos "pedaços" que escreve dos tamanhos de bloco mais regulares, maiores nos quais as correntes se escrevem o mais eficientemente (a dispositivos periféricos, arquivos no sistema de arquivos ou a rede, por exemplo).

BufferedOutputStream é uma de duas classes na biblioteca de Java para implementar flush(), que empurra os bytes que escreveu pelo buffer e fora outro lado. Como armazenar em buffer é tão valioso, poderia lamentar que cada corrente de produção não possa armazenar-se em buffer de qualquer maneira. Afortunadamente, pode rodear qualquer corrente de produção de tal modo para realizar somente que:

OutputStream  s = new BufferedOutputStream(new FileOutputStream("foo"));

Agora tem uma corrente de produção armazenada em buffer baseada no arquivo foo que pode lavar-se com água.

Tal como para o filtro introduzem correntes, qualquer capacidade fornecida por uma corrente de produção de filtro pode usar-se por qualquer outra corrente básica via o aninhamento, e qualquer combinação destas capacidades, em qualquer ordem, pode realizar-se como facilmente pelo aninhamento as próprias correntes de filtro.

DataOutputStream

Todos os métodos que os exemplos desta classe entendem definem-se em uma interface separada, que tanto DataOutputStream como RandomAccessFile implementam. Esta interface é bastante de uso geral que poderia querer usá-la você mesmo nas classes que cria. Chama-se DataOutput.

A interface de DataOutput

Na cooperação com sua interface de inversão de irmão, DataInput, DataOutput fornece um nível mais alto, aproximação de corrente datilografada da leitura e a escrita de dados. Em vez de tratar com bytes, esta interface negócios com escrita dos tipos primitivos da língua de Java diretamente:

void  write(int i)                                    throws IOException;
void  write(byte[]  buffer)                           throws IOException;
void  write(byte[]  buffer, int  offset, int  length) throws IOException;

void  writeBoolean(boolean b) throws IOException;
void  writeByte(int i)        throws IOException;
void  writeShort(int i)       throws IOException;
void  writeChar(int i)        throws IOException;
void  writeInt(int i)         throws IOException;
void  writeLong(long l)       throws IOException;
void  writeFloat(float f)     throws IOException;
void  writeDouble(double d)   throws IOException;

void  writeBytes(String s) throws IOException;
void  writeChars(String s) throws IOException;
void  writeUTF(String s)   throws IOException;

A maioria destes métodos têm cópias na interface DataInput.

Três primeiros métodos refletem as três formas de write() que viu anteriormente. Cada um de oito seguintes métodos escreve um tipo primitivo. Três métodos finais escrevem uma cadeia de bytes ou carateres à corrente: o primeiro como bytes de 8 bits; o segundo, como carateres de Unicode de 16 bits; e o último, como uma corrente de Unicode especial (legível por readUTF() de DataInput).

Observar
Os métodos lidos não assinados em DataInput não têm cópias aqui. Pode escrever os dados dos quais precisam via métodos assinados de DataOutput porque aceitam argumentos de int e também porque escrevem o número correto de bits do número inteiro não assinado de um tamanho dado como um efeito de lado de escrever o número inteiro assinado daquele mesmo tamanho. É o método que lê este número inteiro que deve interpretar o bit de sinal corretamente; o emprego do escritor é fácil.

Agora que sabe o que a interface a que os instrumentos de DataOutputStream parecem, nos deixa vê-lo na ação:

DataOutputStream  s    = new DataOutputStream(myRecordOutputStream());
long              size = getNumberOfItemsInNumericStream();

s.writeLong(size);

for (int  i = 0;  i < size;  ++i) {
    if (shouldProcessNumber(i)) {
        s.writeBoolean(true);     // should process this item
        s.writeInt(theIntegerForItemNumber(i));
        s.writeShort(theMagicBitFlagsForItemNumber(i));
        s.writeDouble(theDoubleForItemNumber(i));
    } else
        s.writeBoolean(false);
}

Isto é a inversão exata do exemplo que se deu para DataInput. Em conjunto, formam um par que pode comunicar uma determinada tabela de tipos primitivos estruturados através de qualquer corrente (ou "transportam a camada"). Use este par como um salto - do ponto sempre que tenha de fazer algo semelhante.

Além da interface precedente, a própria classe implementa um (evidente) método de serviço:

int  theNumberOfBytesWrittenSoFar = s.size();
Processamento de um arquivo

Um dos idiomas mais comuns na entrada-saída de arquivo deve abrir um arquivo, ler e processá-lo linha por linha e produção ele novamente a outro arquivo. Aqui está um exemplo prototípico de como isto se faria em Java:

DataInput   aDI = new DataInputStream(new FileInputStream("source"));
DataOutput  aDO = new DataOutputStream(new FileOutputStream("dest"));
String      line;

while ((line = aDI.readLine()) != null) {
    StringBuffer  modifiedLine = new StringBuffer(line);

    . . .      // process modifiedLine in place
    aDO.writeBytes(modifiedLine.toString());
}
aDI.close();
aDO.close();

Se quiser processá-lo byte por byte, use isto:

try {
    while (true) {
        byte  b = (byte) aDI.readByte();

        . . .      // process b in place
        aDO.writeByte(b);
    }
} finally {
    aDI.close();
    aDO.close();
}

Aqui está um de dois vapores de carreira atraente que somente copia o arquivo:

try { while (true) aDO.writeByte(aDI.readByte()); }
finally { aDI.close(); aDO.close(); }

Aviso
Supõe-se que muitos dos exemplos na lição de hoje (bem como os dois últimos) apareçam dentro de um método que tem IOException na sua cláusula throws, portanto não têm de importunar com catch ing aquelas exceções e manejo deles mais razoavelmente. O seu código deve ser um pouco menos cavaleiro.

PrintStream

Não pode realizá-lo, mas já é intimamente familiar com o uso de dois métodos da classe de PrintStream. É porque sempre que use estas chamadas de método:

System.out.print(. . .)
System.out.println(. . .)

usa de fato um exemplo de PrintStream localizado na variável de classe de System out para executar a produção. System.err também é um PrintStream, e System.in é um InputStream.

Observar
Em sistemas UNIX, estas três correntes vão se anexar a produção padrão, erro padrão e entrada padrão, respectivamente.

PrintStream é unicamente uma classe de corrente de produção (não tem classe de irmão). Como se anexa normalmente a um dispositivo de saída de tela de alguma espécie, fornece uma implementação de flush(). Também fornece o close() familiar e métodos de write(), bem como uma pletora de escolhas da liberação os tipos primitivos e String s de Java:

public void  write(int b);
public void  write(byte[]  buffer, int  offset, int  length);
public void  flush();
public void  close();

public void  print(Object o);
public void  print(String s);
public void  print(char[]  buffer);
public void  print(char c);
public void  print(int i);
public void  print(long l);
public void  print(float f);
public void  print(double d);
public void  print(boolean b);

public void  println(Object o);
public void  println(String s);
public void  println(char[]  buffer);
public void  println(char c);
public void  println(int i);
public void  println(long l);
public void  println(float f);
public void  println(double d);
public void  println(boolean b);

public void  println();   // output a blank line

PrintStream também pode agasalhar-se em volta de qualquer corrente de produção, como uma classe de filtro:

PrintStream  s = new PrintStream(new FileOutputStream("foo"));

s.println("Here's the first line of text in the file foo.");

Se fornecer um segundo argumento ao construtor de PrintStream, que o segundo argumento é um booleano que especifica se a corrente deve autocorar. Se true, um flush() se enviar depois que cada caráter newline escreve-se.

Aqui está um programa de mostra simples que funciona como o UNIX ordenam cat, tomando a entrada padrão, linha por linha e liberação ele à produção padrão:

import java.io.*;   // the one time in the chapter we'll say this

public class  Cat {
    public static void  main(String argv[]) {
        DataInput  d = new DataInputStream(System.in);
        String     line;

     try {  while ((line = d.readLine()) != null)
            System.out.println(line);
        } catch (IOException  ignored) { }
    }
}

PipedOutputStream

Junto com PipedInputStream, este par de classes apoia uma conexão UNIX-pipe-like entre dois fios, implementando toda a sincronização cuidadosa que permite a este tipo "da fila compartilhada" funcionar seguramente. Use o seguinte para fundar a conexão:

PipedInputStream   sIn  = PipedInputStream();
PipedOutputStream  sOut = PipedOutputStream(sIn);

Um fio escreve a sOut; o outro lê em sIn. Fundando dois tais pares, os fios podem comunicar-se seguramente em ambas as direções.

Classes relacionadas

Outras classes e as interfaces em java.io complementam as correntes para fornecer um sistema de entrada-saída completo. Três deles descrevem-se aqui.

Os arquivos de resumos de classe de File de um modo independente da plataforma. Considerando um nome de arquivo, pode responder a perguntas sobre o tipo, posição e propriedades de um arquivo ou diretório no sistema de arquivos.

Um RandomAccessFile cria-se dado um arquivo, um nome de arquivo ou um descritor de arquivo. Combina-se em implementações de classe do DataInput e interfaces de DataOutput, ambos sintonizados para "o acesso casual" a um arquivo no sistema de arquivos. Além destas interfaces, RandomAccessFile fornece certas facilidades parecidas a UNIX tradicionais, como busca de um ponto casual no arquivo.

Finalmente, a classe de StreamTokenizer toma uma corrente de entrada e produz uma sequência de símbolos. Ignorando os seus vários métodos nas suas próprias subclasses, pode criar analisadores gramaticais lexicais potentes.

Pode aprender mais sobre algum e todas destas outras classes das descrições API cheias (online) no seu lançamento de Java.

Seriação de objeto (Java 1.1)

Um tópico a correntes e aquele que estará disponível na biblioteca de Java principal com Java 1.1, são a seriação de objeto. A seriação é a capacidade de escrever um objeto de Java a uma corrente como um arquivo ou uma conexão de rede, e logo lê-la e reconstruir aquele objeto de outro lado. A seriação de objeto é crucial para a capacidade de salvar objetos de Java a um arquivo (o que chamou a persistência de objeto), ou ser capaz de realizar aplicações baseadas na rede que utilizam Remote Method Invocation (RMI)-a capacidade da qual aprenderá mais no Dia 27, "a Extensão Padrão APIs".

No coração do objeto seriação são duas classes de correntes: ObjectInputStream, que herda de DataInputStream e ObjectOutputStream, que herda de DataOutputStream. Ambas destas classes serão parte do pacote de java.io e vão se usar muito do mesmo modo que as correntes de entrada e saída padrão são. Além disso, duas interfaces, ObjectOutput e ObjectInput, que herdam de DataInput e DataOutput, respectivamente, fornecerão o comportamento abstrato para ler e escrever objetos.

Para usar o ObjectInputStream e classes de ObjectOutputStream, cria novos exemplos muito de mesmo modo faz correntes ordinárias, e logo usa o readObject() e métodos de writeObject() para ler e escrever objetos a e daquelas correntes.

O método de writeObject() DE ObjectOutputStream, que toma um argumento de objeto único, serializa aquele objeto bem como qualquer objeto para o qual tem referências. Outros objetos escritos à mesma corrente serializam-se também, com referências para a pista guardada de objetos já serializados de e referências circulares conservadas.

O método de readObject() DE ObjectInputStream não toma nenhum argumento e lê um objeto na corrente (precisará de lançar aquele objeto a um objeto da classe apropriada). Os objetos leem-se na corrente na mesma ordem na qual se escrevem.

Aqui está um exemplo simples da especificação de seriação de objeto que escreve uma data a um arquivo (de fato, escreve uma etiqueta de cadeia, "Today", e logo um objeto de Date):

FileOutputStream f = new FileOutputStream("tmp");
ObjectOutput  s  =  new  ObjectOutputStream(f);
s.writeObject("Today");
s.writeObject(new Date());
s.flush();

A deserialize o objeto (o leem atrás em novamente), use este código:

FileInputStream in = new FileInputStream("tmp");
ObjectInputStream s = new ObjectInputStream(in);
String today = (String)s.readObject();
Date date = (Date)s.readObject();

Uma outra característica da seriação de objeto para observar é o modificador de transient. Usado em declarações de variável de exemplo como outros modificadores são, o modificador de transient significa que o valor daquele objeto não deve guardar-se quando o objeto se serializa - que o seu valor é temporário ou precisará de recriar-se do zero uma vez que o objeto se reconstrói. Use variáveis passageiras para a informação específica para o ambiente (como maçanetas de arquivo que podem ser diferentes de um lado da seriação ao outro) ou por valores que podem recalcular-se facilmente para salvar o espaço no objeto serializado do final.

Para declarar uma variável passageira, use o modificador de transient de maneira faz outros modificadores como public, private ou abstract:

public transient int transientValue = 4;

No momento desta escrita, a seriação de objeto está disponível como um pacote adicional para Java 1.0.2 como parte do pacote RMI. Pode descobrir mais sobre ele, inclusive especificações cheias e software carregável, de Java Web site de RMI em http://chatsubo.javasoft.com/current/.

Sumário

Hoje aprendeu sobre a ideia geral de correntes e encontrou correntes de entrada baseadas em tabelas de byte, arquivos, tubos, sequências de outras correntes, e estica buffers, bem como introduz filtros de armazenar em buffer, dados datilografados, numeração de linha e carateres de empurrar atrás.

Também encontrou as correntes de produção de irmão análogas de tabelas de byte, arquivos, e tubos, filtros de produção de armazenar em buffer e datilografou dados e o filtro de produção único usado para a impressão.

Ao longo do caminho, ficou familiar com os métodos fundamentais que todas as correntes entendem (como read() e write()), bem como os métodos únicos que muitas correntes acrescentam a este repertório. Aprendeu sobre a captura de IOException s-especially o mais útil deles, EOFException.

Finalmente, o DataInput duas vezes útil e as interfaces de DataOutput formaram o coração de RandomAccessFile, uma de várias classes de serviço que preenchem facilidades de entrada-saída de Java.

As correntes de Java fornecem uma base potente na qual pode construir interfaces multienfiadas, correm das espécies mais complexas e os programas (como HotJava) para interpretá-los. Os protocolos de Internet de nível mais alto e os serviços do futuro que o seu applets pode basear-se nesta base só realmente limitam-se pela sua imaginação.

Perguntas e Respostas

Q:
Em um primeiro exemplo de read(), fez algo com o byteOrMinus1 variável que pareceu um pouco desgracioso. Não está lá um melhor caminho? Se não, porque recomendam a forma depois?
A:
Sim, há algo um pouco ímpar sobre aquelas afirmações. Poderia tentar-se a tentar algo como isto em vez disso:

enquanto ((b = (byte) s.read ())! =-1) {
    ...    //processam o byte b
}

O problema com este atalho ocorre se read() devolver o valor 0xFF (0377). Por causa do caminho os valores lançam-se, parecerá ser idêntico ao valor inteiro -1 que indica o fim da corrente. Só salvar aquele valor em uma variável de número inteiro separada, e logo lançá-lo depois, realizarão o resultado desejado. A forma a byte recomenda-se na nota por razões ligeiramente diferentes do que isto, os valores inteiros guardam contudo em variáveis corretamente classificadas são o sempre bom estilo (e além disso, read() realmente deve estar devolvendo algo do tamanho de byte aqui e lançando uma exceção pelo fim da corrente).

Q:
Que correntes de entrada em java.io de fato implementam mark(), reset() e markSupported()?
A:
Próprio InputStream faz - e nas suas implementações à revelia, markSupported() devolve false, mark() não faz nada, e reset() lança uma exceção. A única corrente de entrada no lançamento atual que corretamente apoia a marcação é BufferedInputStream, que ignora estes defaults. LineNumberInputStream de fato implementa mark() e reset(), mas no lançamento atual, não responde markSupported() corretamente, portanto parece como se não faça.
Q:
Porque available() é útil, se às vezes der a resposta incorreta?
A:
Em primeiro lugar, para muitas correntes, dá a resposta direita. Em segundo lugar, para algumas correntes de rede, a sua implementação poderia estar enviando uma pergunta especial para descobrir alguma informação não pode adquirir nenhum outro caminho (por exemplo, o tamanho de um arquivo que se transfere por ftp). Se estiver expondo uma "barra de progresso" de rede ou transferências de arquivos, por exemplo, available() muitas vezes lhe dará o tamanho total da transferência, e quando não faz devolvendo 0 - será óbvio para você (e os seus usuários).
Q:
O que é um bom exemplo do uso do DataInput/DataOutput o par de interfaces?
A:
Um uso comum de tal par consiste em quando os objetos querem "conservar-se em escabeche" para armazenamento ou movimento sobre uma rede. Cada objeto implementa leem e escrevem métodos usando estas interfaces, efetivamente convertendo-se a uma corrente que pode reconstituir-se depois "em outro fim" em uma cópia do objeto original.