A IO não se sente muito relacionada à multi-thread, mas a NIO mudou a maneira como os threads são usados no nível do aplicativo e resolveram algumas dificuldades práticas. O AIO é o IO assíncrono e a série anterior também está relacionada. Aqui, para aprender e gravar, também escrevi um artigo para introduzir o NIO e o AIO.
1. O que é Nio
Nio é a abreviação de nova E/S, que é oposta ao método de E/S antigo baseado em fluxo. A julgar pelo nome, representa um novo conjunto de padrões de E/S Java. Foi incorporado ao JDK no Java 1.4 e possui os seguintes recursos:
Todas as operações de leitura e gravação do canal devem passar por um buffer, e o canal é a abstração de IO, e a outra extremidade do canal é o arquivo manipulado.
2. Buffer
Implementação de buffer em Java. Tipos de dados básicos têm seus buffers correspondentes
Exemplos simples de usar o buffer:
teste de pacote; importar java.io.file; importar java.io.fileInputStream; importar java.nio.bytebuffer; importar java.nio.channels.FileChannel; Public class Test {public static void main (string [] args) lança Exceção {FileInputStream fin = new FileInputStream (novo arquivo ("d: //temp_buffer.tmp")); Filechannel fc = fin.getChannel (); Bytebuffer bytebuffer = bytebuffer.allocate (1024); fc.read (bytebuffer); fc.close (); bytebuffer.flip (); // Leia e grava conversão}}As etapas a serem usadas estão resumidas:
1. Obtenha canal
2. Aplique um buffer
3. Estabeleça uma relação de leitura/gravação entre canal e buffer
4. Feche
O exemplo a seguir é usar o NIO para copiar arquivos:
public static void niocopyFile (Recurso da String, String Destination) lança IoException {FileInputStream fis = new FileInputStream (Resource); FileOutputStream fos = new FileOutputStream (destino); Filechannel readchannel = fis.getChannel (); // leia o canal de arquivo filechannel writeChannel = fos.getChannel (); // grava o canal de arquivo buffer buffer = bytebuffer.allocate (1024); // leia no cache de dados while (true) {buffer.clear (); int len = readchannel.read (buffer); // leia nos dados if (len == -1) {break; // leia no} buffer.flip (); writechannel.write (buffer); // grava no arquivo} readchannel.close (); writechannel.close (); }Existem 3 parâmetros importantes no buffer: posição, capacidade e limite
Aqui precisamos distinguir entre capacidade e limite superior. Por exemplo, se um buffer tiver 10kb, 10kb é a capacidade. Se eu ler o arquivo de 5 KB no buffer, o limite superior será de 5kb.
Aqui está um exemplo para entender esses três parâmetros importantes:
public static void main (string [] args) lança exceção {bytebuffer b = bytebuffer.allocate (15); // Buffer de 15 bytes System.out.println ("limite =" + b.limit () + "capacidade =" + b.capacity () + "position =" + b.position ()); for (int i = 0; i <10; i ++) {// salvar 10 bytes de dados b.put ((byte) i); } System.out.println ("limite =" + b.limit () + "capacidade =" + b.capacity () + "position =" + b.position ()); b.flip (); // Redefinir System.out.println ("limite =" + b.limit () + "capacidade =" + b.capacity () + "position =" + b.Position ()); for (int i = 0; i <5; i ++) {System.out.print (b.get ()); } System.out.println (); System.out.println ("limite =" + b.limit () + "capacidade =" + b.capacity () + "position =" + b.position ()); b.flip (); System.out.println ("limite =" + b.limit () + "capacidade =" + b.capacity () + "position =" + b.position ()); }Todo o processo é mostrado na figura:
Neste momento, a posição é de 0 a 10, e a capacidade e o limite permanecem inalterados.
Esta operação redefine a posição. Geralmente, ao converter o buffer do modo de gravação em modo de leitura, você precisa executar esse método. A operação flip () não apenas redefine a posição atual para 0, mas também define o limite para a posição da posição atual.
O significado do limite é determinar quais dados são significativos. Em outras palavras, os dados da posição para limitar são dados significativos, porque são os dados da última operação. Portanto, as operações de flip geralmente significam leitura e escrita de conversão.
O mesmo significado que acima.
A maioria dos métodos no buffer altera estes 3 parâmetros para alcançar certas funções:
Buffer final público Rewind ()
Definir a posição zero e marca clara
Buffer Final Public Clear ()
Definir a posição zero, e definir limite para o tamanho da capacidade e limpar a marca da bandeira
Buffer Final Public Flip ()
Primeiro limite definido para a posição em que a posição está, depois defina a posição para zero e limpar a marca de bit de bandeira, geralmente usada durante a conversão de leitura e gravação
Arquivo mapeado para a memória
public static void main (string [] args) lança Exceção {RandomAccessFile RAF = new RandomAccessFile ("c: //mapfile.txt", "rw"); Filechannel fc = raf.getChannel (); // mapeia o arquivo no Memory mapedbyteBuffer mbb = fc.map (filechannel.mapmode.read_write, 0, raf.length ()); while (mbb.hasRemaining ()) {System.out.print ((char) mbb.get ()); } mbb.put (0, (byte) 98); // modifica o arquivo raf.close (); }A modificação do MappedByteBuffer é equivalente a modificar o próprio arquivo, portanto a velocidade de operação é muito rápida.
3. Canal
Estrutura geral do servidor de rede multitreado:
Servidor simples multi-thread:
public static void main (string [] args) lança exceção {serversocket echoserver = null; Socket clientSocket = null; tente {echoserver = new ServerSocket (8000); } catch (ioexception e) {System.out.println (e); } while (true) {try {clusterSocket = echoserver.accept (); System.out.println (clusterSocket.getRemoteSocketAddress () + "Connect!"); tp.execute (novo handlemsg (clientes)); } catch (ioexception e) {System.out.println (e); }}}A função é gravar os dados de volta para o cliente sempre que o servidor lê.
Aqui, o TP é um pool de threads, e o HandleMsg é a classe que lida com mensagens.
classe estática handlemsg implementa runnable {omita parte da informação public void run () {try {is = new BufferredReader (new InputStreamReader (clusterSocket.getInputStream ())); OS = new PrintWriter (clusterSocket.getOutputStream (), true); // Leia os dados enviados pelo cliente a partir da string inputStream inputline = null; long b = system.currenttimemillis (); while ((inputline = is.readLine ())! = null) {os.println (inputline); } longo e = sistema. currenttimemillis (); Sistema. out.println ("gase:"+(e - b)+"ms"); } catch (ioexception e) {e.printStackTrace (); } finalmente {feche recurso}}}Cliente:
public static void main (string [] args) lança exceção {socket client = null; PrintWriter Writer = NULL; Leitor buffarreder leitor = null; tente {client = new Socket (); client.connect (new inetSocketAddress ("localhost", 8000)); writer = new PrintWriter (client.getOutputStream (), true); writer.println ("Hello!"); writer.flush (); leitor = new buffarrederReader (new inputStreamReader (client.getInputStream ())); System.out.println ("do servidor:" + leitor.readline ()); } catch (Exceção e) {} finalmente {// omita o fechamento de recursos}}A programação de rede acima é muito básica e haverá alguns problemas usando este método:
Use um thread para cada cliente. Se o cliente tiver uma exceção como atraso, o tópico poderá ser ocupado por um longo tempo. Porque a preparação e a leitura dos dados estão neste tópico. No momento, se houver muitos clientes, isso pode consumir muitos recursos do sistema.
Solução:
Use o NIO não bloqueador (leia os dados sem esperar, os dados estão prontos antes de trabalhar)
Para refletir a eficiência do uso do NIO.
Aqui, primeiro simulamos um cliente ineficiente para simular o atraso devido à rede:
Executores estáticos privados TP = executores.newcachedthreadpool (); private estático final Int Sleep_time = 1000*1000*1000; classe estática public estática implementa Runnable {public void run () {try {client = new Socket (); client.connect (new inetSocketAddress ("localhost", 8000)); writer = new PrintWriter (client.getOutputStream (), true); writer.print ("h"); LockSupport.parknanos (Sleep_time); writer.print ("e"); LockSupport.parknanos (Sleep_time); writer.print ("L"); LockSupport.parknanos (Sleep_time); writer.print ("L"); LockSupport.parknanos (Sleep_time); writer.print ("O"); LockSupport.parknanos (Sleep_time); writer.print ("!"); LockSupport.parknanos (Sleep_time); writer.println (); writer.flush (); } catch (Exceção e) {}}}Saída do lado do servidor:
gasto: 6000ms
gasto: 6000ms
gasto: 6000ms
gasto: 6001ms
gasto: 6002ms
gasto: 6002ms
gasto: 6002ms
gasto: 6002ms
gasto: 6003ms
gasto: 6003ms
porque
while ((inputline = is.readline ())! = null)
Está bloqueado, então o tempo é gasto esperando.
O que aconteceria se Nio fosse usado para lidar com esse problema?
Uma das grandes características do NIO é: notifique -me se os dados estiverem prontos
O canal é um pouco semelhante aos fluxos e um canal pode corresponder a arquivos ou soquetes de rede.
O seletor é um seletor que pode selecionar um canal e fazer alguma coisa.
Um encadeamento pode corresponder a um seletor e um seletor pode pesquisar vários canais e cada canal possui um soquete.
Comparado com o encadeamento acima correspondente a um soquete, após o uso do NIO, um encadeamento pode pesquisar vários soquetes.
Quando o seletor chama select (), ele verificará se algum cliente preparou os dados. Quando nenhum dado estiver pronto, selecione () será bloqueado. Geralmente, é dito que o NIO não é bloqueador, mas se os dados não estiverem prontos, ainda haverá bloqueio.
Quando os dados estiverem prontos, depois de ligar para selecionar (), um SelectionKey será retornado. O seleção Key indica que os dados de um canal em um seletor foram preparados.
Este canal será selecionado apenas quando os dados estiverem prontos.
Dessa forma, o NIO implementa um thread para monitorar vários clientes.
O cliente com o atraso da rede acabado de simular não afetará os threads sob o NIO, porque quando um soquete da rede atrasa, os dados não estão prontos e o seletor não o selecionará, mas selecionará outros clientes preparados.
A diferença entre selectNow () e Select () é que o selectNow () não bloqueia. Quando nenhum cliente prepara dados, o selectNow () não bloqueará e retornará 0. Quando um cliente prepara dados, selectNow () retorna o número de clientes preparados.
Código principal:
teste de pacote; importar java.net.inetAddress; importar java.net.inetsocketaddress; importar java.net.socket; importar java.nio.channels.selectionKey; import java.nio.channels.selector; import java.nio.channels.selector; java.nio.channels.socketchannel; importar java.nio.channels.spi.absttractSelector; importar java.nio.channels.spi.selectorProvider; import javap.util.hashmap; import java.util.iterator; import javap.util.LinkledList; java.util.set; importar java.util.concurrent.executorService; importar java.util.concurrent.executores; classe pública multithreadnioechoserver {public static map <soquete, long> geym_time_stat = new hashmap <soquete, long> (); classe ECHOCLIENT {private LinkedList <TETEBUFFER> outq; ECHOCLIENT () {outq = new LinkedList <TETEBUFFER> (); } public LinkedList <TETEBUFFER> getOutputQueue () {return outq; } public void Enqueue (bytebuffer bb) {outq.addfirst (bb); }} classe handlemsg implementa Runnable {SelectionKey SK; Bytebuffer bb; public Handlemsg (SelectionKey SK, bytebuffer bb) {super (); this.sk = sk; this.bb = bb; } @Override public void run () {// TODO Método Gerado ECHOCLIENT ECHOCLIENT = (ECHOCLIENT) sk.attachment (); echoclient.enqueue (BB); sk.inteSTOPS (SelectionKey.op_Read | SelectionKey.OP_WRITE); Selector.wakeup (); }} seletor de seletor privado; ExecutorService privado tp = executores.newcachedthreadpool (); private void startServer () lança Exceção {Selector = SelectorProvider.Provider (). OpenSelector (); ServerSocketchAnnel sc = serversocketchannel.open (); sc.configureblocking (falso); InetSocketAddress isa = new inetSocketAddress (8000); ssc.socket (). Bind (ISA); // Registre o evento de interesse, aqui está interessado no accpet seleção seleçãokeyKeyKey = ssc.register (selector, selectionKey.op_accept); para (;;) {selettor.Select (); Set readykeys = selettor.lectectedkeys (); Iterator i = prontaykeys.iterator (); longo e = 0; while (i.hasNext ()) {seleçãoKey SK = (SelectionKey) i.Next (); i.Remove (); if (sk.isacceptable ()) {doACept (sk); } else if (sk.isValid () && sk.isReAtable ()) {if (! geym_time_stat.containskey (((socketchannel) sk .channel ()) .socket ())) {geym_time_stat. (((sockankann) sk.channnel (). } Doread (SK); } else if (sk.isvalid () && sk.iswritable ()) {dowrite (sk); e = System.currenttimemillis (); longo b = geym_time_stat.remove (((socketchannel) sk .channel ()). soket ()); System.out.println ("gase:" + (e - b) + "ms"); }}}} private void Dowrite (SelectionKey SK) {// TODO Method Auto-Gerated Stub SocketchAnel Channel = (Socketchannel) sk.Channel (); ECHOCLIENT ECHOCLIENT = (ECHOCLIENT) sk.attachment (); LinkedList <TETEBUFFER> outq = eCHOCLIENT.GetOutQueue (); Bytebuffer bb = outq.getLast (); tente {int len = canal.write (bb); if (len == -1) {desconect (sk); retornar; } if (bb.remaining () == 0) {outq.RemovelSt (); }} catch (Exceção e) {// TODO: lidera a exceção desconectada (sk); } if (outq.size () == 0) {sk.intestops (SelectionKey.op_read); }} private void Doread (seleçãoKey SK) {// TODO Método Auto-Gerado Stub SocketchAnel Channel = (Socketchannel) Sk.Channel (); Bytebuffer bb = bytebuffer.allocate (8192); int len; tente {len = canal.read (bb); if (len <0) {desconect (sk); retornar; }} catch (Exceção e) {// TODO: lidera a exceção desconectada (sk); retornar; } bb.flip (); tp.execute (novo handlemsg (sk, bb)); } private void desconect (seleçãoKey sk) {// ToDO Método Geralizado Auto-Goldado // omitir a operação de fechamento seco} DoACcept de vazio privado (seleçãoKey SK) {// TODO Método Auto-Gerado Stub ServerSocketchAnel Server = (ServerSocketchAnnel) Sk.Channel (); Socketchannel ClientChannel; tente {clientChannel = server.accept (); clientChannel.configureblocking (false); SelectionKey ClientKey = clientChannel.register (Selector, SelectionKey.op_read); ECHOCLINET ECHOCLIENT = new ECHOCLIENT (); clientKey.Attach (EchocLinnet); INETAddress clientAddress = clientChannel.socket (). GetineTAddress (); System.out.println ("conexão aceita de" + clientAddress.gethostAddress ()); } Catch (Exceção e) {// TODO: HOLANDE Exceção}} public static void main (String [] args) {// TODO Método GOERADO AUTOMELADO Método MultithReadNioechoserver ECHOSERVER = new MultithReadnioechoserver (); tente {echoserver.startServer (); } Catch (Exceção e) {// TODO: HODE EXCEÇÃO}}}O código é apenas para referência e seu principal recurso é que você está interessado em eventos diferentes para fazer coisas diferentes.
Ao usar o cliente atrasado que foi simulado anteriormente, o consumo de tempo desta vez está entre 2ms e 11ms. A melhoria do desempenho é óbvia.
Resumir:
1. Depois que o NIO prepara os dados, ele o entregará ao aplicativo de processamento. O processo de leitura/escrita de dados ainda está concluído no thread do aplicativo e apenas retirará o tempo de espera para um thread separado.
2. Salvar o tempo de preparação de dados (porque o seletor pode ser reutilizado)
5. AIO
Recursos de AIO:
1. Notifique -me depois de ler
2. IO não será acelerado, mas será notificado depois de lê -lo
3. Use funções de retorno de chamada para executar o processamento de negócios
Código relacionado à AIO:
Assíncrononserversocketchannel
servidor = assíncronserversocketchannel.open (). bind (new inetSocketAddress (porta));
Use o método de aceitação no servidor
Resumo público <a> Void aceita (um anexo, conclusão -manipulador <assíncronsocketchannel,? Super A> Handler);
ConclionHandler é uma interface de retorno de chamada. Quando há um cliente aceita, faz o que está no manipulador.
Código de exemplo:
server.accept (null, novo conclusão -Handler <Asincronsocketchannel, object> () {Final ByteBuffer buffer = byteBuffer.Allocate (1024); public void concluído (ascronsocketchAnnel Result, objeto Anexamento) {System.out.Println (Thread.currThen. {buffer.clear (); servidor.Acept (nulo, este);Aqui usamos o futuro para obter retorno instantâneo. Para o futuro, consulte o artigo anterior
Com base no entendimento da NIO, olhando para a AIO, a diferença é que a AIO aguarda o processo de leitura e gravação para chamar a função de retorno de chamada antes de concluir.
Nio é síncrono e não bloqueando
AIO é assíncrono e não bloqueado
Como o processo de leitura e gravação da NIO ainda está concluído no tópico do aplicativo, o NIO não é adequado para aqueles com processo de leitura e gravação longos.
O processo de leitura e gravação da AIO é notificado apenas após a conclusão, portanto, a AIO é competente para tarefas de processo de leitura e gravação de peso pesado e de longo prazo.