Java NIO (New Input/Output) - o novo pacote API de entrada/saída - foi introduzido no J2SE 1.4 em 2002. O objetivo do Java NIO é melhorar o desempenho de tarefas com uso intensivo de E/S na plataforma Java. Dez anos depois, muitos desenvolvedores Java ainda não sabem como fazer uso completo do NIO, e ainda menos pessoas sabem que a API de entrada/saída atualizada (NIO.2) foi introduzida no Java SE 7. A maior contribuição do NIO e NIO.2 para a plataforma Java é melhorar o desempenho de um componente central no desenvolvimento de aplicativos Java: processamento de entrada/saída. No entanto, nenhum dos pacotes é muito útil e não é adequado para todos os cenários. Se usados corretamente, Java NIO e NIO.2 podem reduzir bastante o tempo gasto em algumas operações de E/S comuns. Esse é o superpoder do NIO e do NIO.2, e neste artigo vou mostrar 5 maneiras simples de usá-los.
Alterar notificações (porque todo evento requer um ouvinte)
Seletores e IO assíncronos: Melhorando a multiplexação por meio de seletores
Canal - Promessa e Realidade
Mapeamento de memória - bom aço é usado na lâmina
Codificação e pesquisa de caracteres
Antecedentes da NIO
Por que um pacote de melhorias que existe há 10 anos é um novo pacote de E/S para Java? A razão é que para a maioria dos programadores Java, as operações básicas de E/S são suficientes. No trabalho diário, a maioria dos desenvolvedores Java não precisa aprender NIO. Indo um passo além, o NIO é mais do que apenas um pacote de melhoria de desempenho. Em vez disso, é uma coleção de diferentes funções relacionadas à E/S Java. NIO alcança melhoria de desempenho tornando o desempenho de aplicativos Java "mais próximo da essência", o que significa que as APIs de NIO e NIO.2 expõem a entrada para operações de sistema de baixo nível. O preço do NIO é que, embora forneça recursos de controle de E/S mais poderosos, ele também exige que usemos e pratiquemos com mais cuidado do que a programação básica de E/S. Outra característica do NIO é seu foco na expressividade da aplicação, que veremos nos exercícios a seguir.
Comece a aprender NIO e NIO.2
Existem muitos materiais de referência para NIO - alguns links selecionados nos materiais de referência. Para aprender NIO e NIO.2, a documentação do Java 2 SDK Standard Edition (SE) e a documentação do Java SE 7 são indispensáveis. Para usar o código deste artigo, você precisa usar o JDK 7 ou superior.
Para muitos desenvolvedores, a primeira vez que encontram o NIO pode ser durante a manutenção de aplicativos: um aplicativo em funcionamento está ficando cada vez mais lento, então algumas pessoas sugerem usar o NIO para melhorar a velocidade de resposta. O NIO é excelente quando se trata de melhorar o desempenho do aplicativo, mas os resultados específicos dependem do sistema subjacente (observe que o NIO depende da plataforma). Se esta é a primeira vez que usa NIO, você precisa pesá-lo com cuidado. Você descobrirá que a capacidade do NIO de melhorar o desempenho depende não apenas do sistema operacional, mas também da JVM que você está usando, do contexto virtual do host, das características do armazenamento em massa e até mesmo dos dados. Portanto, o trabalho de medição de desempenho é relativamente difícil de realizar. Especialmente quando seu sistema possui um ambiente de implantação portátil, você precisa prestar atenção especial.
Depois de compreender o conteúdo acima, não nos preocupemos. Agora vamos experimentar as cinco funções importantes do NIO e do NIO.2.
1. Notificação de alteração (porque cada evento requer um ouvinte)
Uma preocupação comum entre os desenvolvedores interessados em NIO e NIO.2 é o desempenho das aplicações Java. Na minha experiência, o notificador de alteração de arquivo no NIO.2 é o recurso mais interessante (e subestimado) da nova API de entrada/saída.
Muitos aplicativos de nível empresarial requerem processamento especial nas seguintes situações:
Quando um arquivo é carregado em uma pasta FTP
Quando uma definição em uma configuração é modificada
Quando um rascunho de documento é carregado
Quando ocorrem outros eventos do sistema de arquivos
Todos esses são exemplos de notificações ou respostas de alterações. Nas versões anteriores do Java (e de outras linguagens), a pesquisa era a melhor maneira de detectar esses eventos de mudança. A votação é um tipo especial de loop infinito: verifica o sistema de arquivos ou outros objetos e compara-o com o estado anterior. Se não houver alteração, continue a verificação após um intervalo de algumas centenas de milissegundos ou 10 segundos. Isso continua em um loop infinito.
NIO.2 fornece uma maneira melhor de realizar a detecção de alterações. A Listagem 1 é um exemplo simples.
Listagem 1. Mecanismo de notificação de alterações em NIO.2
Copie o código do código da seguinte forma:
importar java.nio.file.attribute.*;
importjava.io.*;
importjava.util.*;
importjava.nio.file.Path;
importjava.nio.file.Paths;
importjava.nio.file.StandardWatchEventKinds;
importjava.nio.file.WatchEvent;
importjava.nio.file.WatchKey;
importjava.nio.file.WatchService;
importjava.util.List;
publicclassWatcher{
publicstaticvoidmain(String[]args){
Paththis_dir=Paths.get(".");
System.out.println("Agora observando o diretório atual...");
tentar{
WatchServicewatcher=this_dir.getFileSystem().newWatchService();
this_dir.register(observador,StandardWatchEventKinds.ENTRY_CREATE);
WatchKeywattckKey=watcher.take();
List<WatchEvent<<64;>>events=watckKey.pollEvents();
for(WatchEventevent:eventos){
System.out.println("Alguém acabou de criar o arquivo'"+event.context().toString()+"'.");
}
}catch(Exceção){
System.out.println("Erro:"+e.toString());
}
}
}
Compile este código e execute-o na linha de comando. No mesmo diretório, crie um novo arquivo, por exemplo, execute o comando touchexample ou copyWatcher.classexample. Você verá a seguinte mensagem de notificação de alteração:
Alguém acabou de criar o campo 'example1'.
Este exemplo simples mostra como começar a usar a funcionalidade JavaNIO. Ao mesmo tempo, também introduz a classe NIO.2 Watcher, que é mais direta e fácil de usar do que o esquema de polling no I/O original.
Cuidado com erros ortográficos
Ao copiar o código deste artigo, tome cuidado com erros de ortografia. Por exemplo, o objeto StandardWatchEventKinds na Listagem 1 está no plural. Mesmo na documentação do Java.net está escrito errado.
Pontas
O mecanismo de notificação no NIO é mais simples de usar do que o antigo método de pesquisa, o que o induzirá a ignorar a análise detalhada de requisitos específicos. Ao usar um ouvinte pela primeira vez, você precisa considerar cuidadosamente a semântica dos conceitos que está usando. Por exemplo, saber quando uma mudança terminará é mais importante do que saber quando ela começará. Esse tipo de análise precisa ser muito cuidadoso, especialmente para cenários comuns como movimentação de pastas FTP. NIO é um pacote muito poderoso, mas também possui algumas “pegadinhas” sutis que podem causar confusão para quem não está familiarizado com ele.
2. Seletores e IO assíncronos: Melhore a multiplexação por meio de seletores
Os recém-chegados ao NIO geralmente o associam a "entrada/saída sem bloqueio". NIO é mais do que apenas E/S sem bloqueio, mas essa percepção não está totalmente errada: a E/S básica do Java está bloqueando a E/S - o que significa que espera até que a operação seja concluída - no entanto, E/S sem bloqueio ou assíncrona é o recurso mais comumente usado do NIO, não de todo o NIO.
A E/S sem bloqueio do NIO é orientada a eventos e é demonstrada no exemplo de escuta do sistema de arquivos na Listagem 1. Isso significa definir um seletor (retorno de chamada ou ouvinte) para um canal de E/S e então o programa pode continuar em execução. Quando ocorre um evento neste seletor - como o recebimento de uma linha de entrada - o seletor "acorda" e é executado. Tudo isso é implementado por meio de um único thread, que é significativamente diferente da E/S padrão do Java.
A Listagem 2 mostra um eco de programa de rede multiporta implementado usando o seletor do NIO. Este é um pequeno programa criado por Greg Travis em 2003 (consulte a lista de recursos). Os sistemas Unix e semelhantes ao Unix há muito implementam seletores eficientes, que são um bom modelo de referência para modelos de programação de alto desempenho em redes Java.
Listagem 2.Seletor NIO
Copie o código do código da seguinte forma:
importjava.io.*;
importjava.net.*;
importjava.nio.*;
importjava.nio.channels.*;
importjava.util.*;
classe públicaMultiPortEcho
{
portas privadas[];
privateByteBufferechoBuffer=ByteBuffer.allocate(1024);
publicMultiPortEcho(intports[])throwsIOException{
this.ports=portas;
configure_selector();
}
privatevoidconfigure_selector()throwsIOException{
//Cria um novo seletor
Selectorselector=Selector.open();
//Abre o ouvinte em cada porta e registra cada um
//com o seletor
for(inti=0;i<portas.length;++i){
ServerSocketChannelssc=ServerSocketChannel.open();
ssc.configureBlocking(falso);
ServerSocketss=ssc.socket();
InetSocketAddressaddress=newInetSocketAddress(portas[i]);
ss.bind(endereço);
SelectionKeykey=ssc.register(seletor,SelectionKey.OP_ACCEPT);
System.out.println("Goingtolistenon"+portas[i]);
}
enquanto(verdadeiro){
intnum=seletor.select();
SetselectedKeys=selector.selectedKeys();
Iteratorit=selectedKeys.iterator();
enquanto(it.hasNext()){
SelectionKeykey=(SelectionKey)it.next();
if((key.readyOps()&SelectionKey.OP_ACCEPT)
==SelectionKey.OP_ACCEPT){
//Aceita a nova conexão
ServerSocketChannelssc=(ServerSocketChannel)key.channel();
SocketChannelsc=ssc.accept();
sc.configureBlocking(falso);
//Adiciona a nova conexão ao seletor
SelectionKeynewKey=sc.register(seletor,SelectionKey.OP_READ);
it.remove();
System.out.println("Obteve conexão de"+sc);
}elseif((key.readyOps()&SelectionKey.OP_READ)
==SelectionKey.OP_READ){
//Lê os dados
SocketChannelsc=(SocketChannel)key.channel();
//Ecodados
intbytesEchoed=0;
enquanto(verdadeiro){
echoBuffer.clear();
intnumber_of_bytes=sc.read(echoBuffer);
if(número_de_bytes<=0){
quebrar;
}
echoBuffer.flip();
sc.write(echoBuffer);
bytesEchoed+=número_de_bytes;
}
System.out.println("Echoed"+bytesEchoed+"from"+sc);
it.remove();
}
}
}
}
staticpublicvoidmain(Stringargs[])throwsException{
if(args.comprimento<=0){
System.err.println("Uso:javaMultiPortEchoport[portport...]");
Sistema.exit(1);
}
intports[]=newint[args.length];
for(inti=0;i<args.length;++i){
portas[i]=Integer.parseInt(args[i]);
}
newMultiPortEcho(portas);
}
}
Compile este código e inicie-o com um comando semelhante a javaMultiPortEcho80058006. Assim que este programa for executado com êxito, inicie um telnet simples ou outro emulador de terminal para conectar-se às interfaces 8005 e 8006. Você verá que este programa ecoa todos os caracteres que recebe - e faz isso através de um thread Java.
3. Passagem: Promessa e Realidade
No NIO, um canal pode representar qualquer objeto que possa ser lido e escrito. Sua função é fornecer abstração para arquivos e soquetes. Os canais NIO suportam um conjunto consistente de métodos para que você não precise prestar atenção especial a diferentes objetos durante a codificação, seja uma saída padrão, uma conexão de rede ou o canal em uso. Esse recurso de canais é herdado de fluxos em E/S básica Java. Os fluxos fornecem canais de E/S de bloqueio que suportam E/S assíncrona.
O NIO é frequentemente recomendado por seu alto desempenho, mas mais precisamente por seus tempos de resposta rápidos. Em alguns cenários, o desempenho do NIO é pior do que o Java I/O básico. Por exemplo, para uma leitura e gravação sequencial simples de um arquivo pequeno, o desempenho obtido simplesmente pelo streaming pode ser duas a três vezes mais rápido do que a implementação de codificação baseada em canal orientada a eventos correspondente. Ao mesmo tempo, canais não multiplexados - ou seja, um canal separado por thread - são muito mais lentos do que canais múltiplos registrando seus seletores no mesmo thread.
Agora, quando você estiver pensando em usar streams ou canais, tente fazer as seguintes perguntas:
Quantos objetos de E/S você precisa ler e escrever?
Os diferentes objetos de E/S são sequenciais ou todos precisam acontecer ao mesmo tempo?
Seus objetos de E/S precisam persistir por um curto período de tempo ou durante toda a vida útil do seu processo?
Sua E/S é adequada para processamento em um único thread ou em vários threads diferentes?
A comunicação de rede e a E/S local têm a mesma aparência ou têm padrões diferentes?
Essa análise é uma prática recomendada ao decidir usar fluxos ou canais. Lembre-se: NIO e NIO.2 não são substitutos de E/S básica, mas um complemento a ela.
4. Mapeamento de memória – bom aço é usado na lâmina
A melhoria de desempenho mais significativa no NIO é o mapeamento de memória. O mapeamento de memória é um serviço de nível de sistema que trata uma seção de um arquivo usado em um programa como memória.
Existem muitos efeitos potenciais do mapeamento de memória, mais do que posso fornecer aqui. Em um nível superior, pode fazer com que o desempenho de E/S de acesso a arquivos atinja a velocidade de acesso à memória. O acesso à memória costuma ser muito mais rápido do que o acesso a arquivos. A Listagem 3 é um exemplo simples de mapa de memória NIO.
Listagem 3. Mapeamento de memória em NIO
Copie o código do código da seguinte forma:
importjava.io.RandomAccessFile;
importjava.nio.MappedByteBuffer;
importjava.nio.channels.FileChannel;
publicclassmem_map_example{
privatestaticintmem_map_size=20*1024*1024;
privatestaticStringfn="example_memory_mapped_file.txt";
publicstaticvoidmain(String[]args)throwsException{
RandomAccessFilememoryMappedFile=newRandomAccessFile(fn,"rw");
//Mapeando um arquivo na memória
MappedByteBufferout=memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE,0,mem_map_size);
//Escrevendo emMemoryMappedFile
for(inti=0;i<mem_map_size;i++){
saída.put((byte)'A');
}
System.out.println("Arquivo'"+fn+"'isnow"+Integer.toString(mem_map_size)+"bytesfull.");
//Ler do arquivo mapeado na memória.
for(inti=0;i<30;i++){
System.out.print((char)out.get(i));
}
System.out.println("/nReadingfrommemory-mappedfile'"+fn+"'iscomplete.");
}
}
Na Listagem 3, este exemplo simples cria um arquivo example_memory_mapped_file.txt de 20M, preenche-o com o caractere A e, em seguida, lê os primeiros 30 bytes. Em aplicações práticas, o mapeamento de memória não só é bom para melhorar a velocidade bruta de E/S, mas também permite que vários leitores e gravadores diferentes processem a mesma imagem de arquivo ao mesmo tempo. Esta tecnologia é poderosa, mas também perigosa, mas se usada corretamente, aumentará sua velocidade de IO várias vezes. Como todos sabemos, as operações comerciais de Wall Street utilizam tecnologia de mapeamento de memória para obter vantagens em segundos ou mesmo milissegundos.
5. Codificação e pesquisa de caracteres
O último recurso do NIO que quero explicar neste artigo é o charset, um pacote usado para converter diferentes codificações de caracteres. Antes do NIO, Java implementava a maior parte da mesma funcionalidade integrada por meio do método getByte. charset é popular porque é mais flexível que getBytes e pode ser implementado em um nível inferior, o que resulta em melhor desempenho. Isso é ainda mais valioso para pesquisar idiomas diferentes do inglês que sejam sensíveis à codificação, ordenação e outros recursos do idioma.
A Listagem 4 mostra um exemplo de conversão de caracteres Unicode em Java para Latin-1
Lista 4. Personagens em NIO
Copie o código do código da seguinte forma:
Stringsome_string="EstaéumastringqueJavanativamentearmazenaUnicode.";
Charsetlatin1_charset=Charset.forName("ISO-8859-1");
CharsetEncodelatin1_encoder=charset.newEncoder();
ByteBufferlatin1_bbuf=latin1_encoder.encode(CharBuffer.wrap(some_string));
Observe que Charsets e canais são projetados para serem usados juntos, para que o programa possa ser executado normalmente quando o mapeamento de memória, E/S assíncrona e conversão de codificação são coordenados.
Resumo: Claro que há mais para saber
O objetivo deste artigo é familiarizar os desenvolvedores Java com alguns dos recursos mais importantes (e úteis) do NIO e NIO.2. Você pode usar a base estabelecida por esses exemplos para entender alguns outros métodos do NIO. Por exemplo, o conhecimento que você aprende sobre canais pode ajudá-lo a entender o processamento de links simbólicos no sistema de arquivos no caminho do NIO; Você também pode consultar a lista de recursos que forneci posteriormente, que fornece alguns documentos para um estudo aprofundado da nova API de E/S do Java.