IO no se siente muy relacionado con el subproceso múltiple, pero NIO ha cambiado la forma en que los subprocesos se usan en el nivel de aplicación y han resuelto algunas dificultades prácticas. AIO es Asynchronous IO y las series anteriores también están relacionadas. Aquí, para aprender y grabar, también escribí un artículo para presentar a Nio y Aio.
1. ¿Qué es Nio?
NIO es la abreviatura de nuevas E/S, que es opuesta al antiguo método de E/S basado en la corriente. A juzgar por el nombre, representa un nuevo conjunto de estándares de E/S de Java. Se incorporó al JDK en Java 1.4 y tiene las siguientes características:
Todas las operaciones de lectura y escritura del canal deben pasar por un búfer, y el canal es la abstracción de IO, y el otro extremo del canal es el archivo manipulado.
2. Amortiguador
Implementación de búfer en Java. Los tipos de datos básicos tienen sus búferes correspondientes
Ejemplos simples del uso de búfer:
prueba de paquete; import java.io.file; import java.io.fileinputstream; import java.nio.bytebuffer; import java.nio.channels.filechannel; Public Class Test {public static void main (string [] args) lanza la excepción {fileInputStream FIN = new FileInputStream (nuevo archivo ("d: //temp_buffer.tmp")); FileChannel fc = fin.getChannel (); Bytebuffer bytebuffer = bytebuffer.allocate (1024); fc.Read (bytebuffer); fc.close (); bytebuffer.flip (); // leer y escribir conversión}}Se resumen los pasos a usar:
1. Obtener canal
2. Solicite un búfer
3. Establezca una relación de lectura/escritura entre canal y búfer
4. Cerrar
El siguiente ejemplo es usar NIO para copiar archivos:
public static void nioCopyFile (String Resource, String Destination) lanza IOException {fileInputStream fis = new FileInputStream (recurso); FileOutputStream fos = new FileOutputStream (destino); FileChannel readChannel = fis.getChannel (); // leer el canal de archivo filechannel writeChannel = fos.getChannel (); // escribir el canal de archivo bytebuffer buffer = bytebuffer.allocate (1024); // Leer en el caché de datos mientras (verdadero) {buffer.clear (); int len = readChannel.read (buffer); // Leer en los datos if (len == -1) {break; // leer en} buffer.flip (); writeChannel.write (búfer); // escribir en el archivo} readchannel.close (); writeChannel.close (); }Hay 3 parámetros importantes en el búfer: posición, capacidad y límite
Aquí necesitamos distinguir entre capacidad y límite superior. Por ejemplo, si un búfer tiene 10 kb, entonces 10 kb es la capacidad. Si leo el archivo de 5kb en el búfer, entonces el límite superior es de 5 kb.
Aquí hay un ejemplo para comprender estos 3 parámetros importantes:
public static void main (string [] args) lanza la excepción {bytebuffer b = bytebuffer.allocate (15); // 15 byte buffer System.out.println ("limit =" + b.limit () + "capacidad =" + b.capacity () + "posicion =" + b.position ()); para (int i = 0; i <10; i ++) {// Guardar 10 bytes de datos B.put ((byte) i); } System.out.println ("Limit =" + B.Limit () + "Capacity =" + B.Capacity () + "Position =" + B.Position ()); b.flip (); // Restablecer la posición System.out.println ("Limit =" + B.Limit () + "Capacity =" + B.Capacity () + "Position =" + B.Position ()); para (int i = 0; i <5; i ++) {System.out.print (B.get ()); } System.out.println (); System.out.println ("limit =" + b.limit () + "capacidad =" + b.capacity () + "posicion =" + b.position ()); b.flip (); System.out.println ("limit =" + b.limit () + "capacidad =" + b.capacity () + "posicion =" + b.position ()); }Todo el proceso se muestra en la figura:
En este momento, la posición es de 0 a 10, y la capacidad y el límite permanecen sin cambios.
Esta operación restablece la posición. Por lo general, al convertir el búfer desde el modo de escritura en el modo de lectura, debe realizar este método. La operación flip () no solo restablece la posición actual en 0, sino que también establece el límite en la posición de la posición actual.
El significado del límite es determinar qué datos son significativos. En otras palabras, los datos de posición a límite son datos significativos porque son los datos de la última operación. Por lo tanto, las operaciones de flip a menudo significan la conversión de lectura y escritura.
El mismo significado que arriba.
La mayoría de los métodos en el búfer cambian estos 3 parámetros para lograr ciertas funciones:
Public Final Buffer Rewind ()
Establecer la posición cero y transparente marca
Buffer Public Final Buff ()
Establezca la posición cero y establezca el límite al tamaño de la capacidad y borre la marca de la bandera
Público Final Buffer Flip ()
Primer límite de establecimiento de la posición donde está la posición, luego establecer la posición en cero y borrar la marca de bits de bandera, generalmente utilizada durante la conversión de lectura y escritura
Archivo asignado a la memoria
public static void main (string [] args) lanza la excepción {randomAccessFile raf = new RandomAccessFile ("c: //mapfile.txt", "rw"); FileChannel fc = raf.getChannel (); // asigna el archivo en memoria mappedByteBuffer mbb = fc.map (filechannel.mapmode.read_write, 0, raf.length ()); while (mbb.hasremaining ()) {system.out.print ((char) mbb.get ()); } mbb.put (0, (byte) 98); // modificar el archivo raf.close (); }La modificación del mappedByteBuffer es equivalente a modificar el archivo en sí, por lo que la velocidad de operación es muy rápida.
3. Canal
Estructura general del servidor de red de múltiples subprocesos:
Servidor simple de múltiples subprocesos:
public static void main (string [] args) lanza la excepción {ServerSocket echoserver = null; Socket clientsocket = null; intente {echoserver = new Serversocket (8000); } catch (ioException e) {System.out.println (e); } while (true) {try {clientsocket = echoserver.accept (); System.out.println (Clientsocket.getRemotesocketAddress () + "Conectar!"); tp.execute (nuevo handlemsg (clientsocket)); } catch (ioException e) {System.out.println (e); }}}La función es escribir los datos al cliente siempre que el servidor lea.
Aquí TP es un grupo de hilos, y HandlemSg es la clase que maneja los mensajes.
La clase estática HandLemSG implementa Runnable {omitir parte de la información public void run () {try {is = new BufferedReader (new InputStreamReader (Clientsocket.getInputStream ())); os = new PrintWriter (Clientsocket.getOutputStream (), true); // Lea los datos enviados por el cliente desde la cadena inputStream inputline = null; largo B = System.CurrentTimemillis (); while ((inputline = is.readline ())! = null) {os.println (inputline); } long e = sistema. CurrentTimemillis (); Sistema. out.println ("gastar:"+(e - b)+"ms"); } catch (ioException e) {E.PrintStackTrace (); } Finalmente {recurso cerrado}}}Cliente:
public static void main (string [] args) lanza la excepción {socket client = null; PrintWriter Writer = null; BufferedReader lector = null; intente {client = new Socket (); client.connect (nuevo inetSocketAddress ("localhost", 8000)); escritor = new PrintWriter (Client.getOutputStream (), true); escritor.println ("¡Hola!"); escritor.flush (); lector = new BufferedReader (new InputStreamReader (Client.getInputStream ())); System.out.println ("Desde el servidor:" + lector.readline ()); } catch (excepción e) {} finalmente {// omitir el cierre de recursos}}La programación de red anterior es muy básica, y habrá algunos problemas utilizando este método:
Use un hilo para cada cliente. Si el cliente experimenta una excepción como el retraso, el hilo puede estar ocupado durante mucho tiempo. Porque la preparación de datos y la lectura están en este hilo. En este momento, si hay muchos clientes, puede consumir muchos recursos del sistema.
Solución:
Use NIO sin bloqueo (lea los datos sin esperar, los datos están listos antes de trabajar)
Para reflejar la eficiencia del uso de NIO.
Aquí primero simulamos un cliente ineficiente para simular el retraso debido a la red:
Private static EjecutorService tp = ejecutors.newCachedThreadPool (); privado estático final int sleep_time = 1000*1000*1000; public static class Echoclient implements runnable {public void run () {try {client = new Socket (); client.connect (nuevo inetSocketAddress ("localhost", 8000)); escritor = new PrintWriter (Client.getOutputStream (), true); escritor.print ("h"); Locksupport.Parknanos (sleep_time); escritor.print ("e"); Locksupport.Parknanos (sleep_time); escritor.print ("l"); Locksupport.Parknanos (sleep_time); escritor.print ("l"); Locksupport.Parknanos (sleep_time); escritor.print ("o"); Locksupport.Parknanos (sleep_time); escritor.print ("!"); Locksupport.Parknanos (sleep_time); escritor.println (); escritor.flush (); } catch (excepción e) {}}}Salida del lado del servidor:
Gastar: 6000 ms
Gastar: 6000 ms
Gastar: 6000 ms
Gasto: 6001 ms
Gasto: 6002 ms
Gasto: 6002 ms
Gasto: 6002 ms
Gasto: 6002 ms
Gasto: 6003 ms
Gasto: 6003 ms
porque
while ((inputline = is.Readline ())! = NULL)
Está bloqueado, por lo que se pasa el tiempo esperando.
¿Qué pasaría si Nio fuera acostumbrado a lidiar con este problema?
Una de las grandes características de NIO es: notificarme si los datos están listos
El canal es un poco similar a las transmisiones, y un canal puede corresponder a archivos o sockets de red.
El selector es un selector que puede seleccionar un canal y hacer algo.
Un hilo puede corresponder a un selector, y un selector puede encuestar múltiples canales, y cada canal tiene un socket.
En comparación con el hilo anterior correspondiente a un enchufe, después de usar NIO, un hilo puede encender múltiples enchufes.
Cuando el selector llame a Select (), verificará si algún cliente ha preparado los datos. Cuando no hay datos listos, select () bloqueará. Por lo general, se dice que NIO no está bloqueado, pero si los datos no están listos, todavía habrá bloqueo.
Cuando los datos están listos, después de llamar a Select (), se devolverá una Key Key. SelectionKey indica que se han preparado los datos de un canal en un selector.
Este canal solo se seleccionará cuando los datos estén listos.
De esta manera, NIO implementa un hilo para monitorear múltiples clientes.
El cliente con el retraso de la red simplemente simulado no afectará los subprocesos en NIO, porque cuando una red de Socket se retrasa, los datos no están listos y el selector no lo seleccionará, sino que seleccionará otros clientes preparados.
La diferencia entre selectnow () y select () es que selectnow () no bloquea. Cuando ningún cliente prepara datos, selectnow () no bloqueará y devolverá 0. Cuando un cliente prepare datos, selectNow () devuelve el número de clientes preparados.
Código principal:
prueba de paquete; import java.net.inetaddress; import java.net.inetsocketaddress; import java.net.socket; import java.nio.channels.selectionkey; import java.nio.channels.selector; import java.nio.channels.selector; import java.nio.channels.serversocketchannel; import; importar; import java.nio.channels.socketchannel; import java.nio.channels.spi.abstractelector; import java.nio.channels.spi.selectorProvider; import java.util.hashmap; import java.util.iterator; import java.util.linkedlist; import java.util.util.map; import; java.util.set; import java.util.concurrent.executorservice; import java.util.concurrent.executors; clase pública multithreadnioechoserver {public static map <Socket, long> geym_time_stat = new Hashmap <Socket, long> (); clase ECHOCLIENT {private LinkedList <bytebuffer> outq; ECHOCLIENT () {outq = new LinkedList <byteBuffer> (); } public LinkedList <byteBuffer> getOutputqueue () {return outq; } public void enqueue (bytebuffer bb) {outq.addfirst (bb); }} clase 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 Auto Generado Método STUB ECHOCLIENT ECHOCLIENT = (ECHOCLIENT) SK.ATTACHMENT (); Echoclient.enqueue (BB); sk.interestops (selectionKey.op_read | selectionKey.op_write); selector.wakeUp (); }} selector de selector privado; privado ejecutorservice tp = ejecutors.newCachedThreadPool (); private void starterver () lanza la excepción {Selector = SelectorProvider.Provider (). OpenSelector (); ServerSocketchannel SSC = ServerSocketchannel.open (); ssc.configureBlocking (falso); InetSocketAddress Isa = nuevo inetSocketAddress (8000); ssc.socket (). bind (ISA); // Registre el evento de interés, aquí está interesado en la selección de eventos ACCPET selectionKey aceptkey = ssc.register (selector, selectionKey.op_accept); para (;;) {selector.select (); Establecer ReadyKeys = Selector.SelectedKeys (); Iterador i = ReadyKeys.iterator (); largo E = 0; while (i.hasnext ()) {selectionKey sk = (selectionKey) I.Next (); i.remove (); if (sk.isaceptable ()) {doaccept (sk); } else if (sk.isvalid () && sk.isreadable ()) {if (! geym_time_stat.containskey (((socketchetchannel) sk .channel ()). Socket ())) {geym_time_stat.put (((socketchannel) sk.channel ()). } doread (sk); } else if (sk.isvalid () && sk.iswritable ()) {dowRite (sk); e = System.CurrentTimemillis (); long b = geym_time_stat.remove (((socketchannel) sk .channel ()). socket ()); System.out.println ("gastar:" + (e - b) + "ms"); }}}} private void dowrite (selectionkey sk) {// tODO método auto-generado stub Socketchannel canal = (Socketchannel) sk.channel (); ECHOCLIENT ECHOCLIENT = (ECHOCLIENT) SK.ATTACHMENT (); LinkedList <byteBuffer> outq = Choclient.getOutputqueue (); Bytebuffer bb = outq.getLast (); intente {int len = channel.write (bb); if (len == -1) {desconectar (sk); devolver; } if (bb.remaining () == 0) {outq.removelast (); }} Catch (Exception e) {// tODO: manejar excepción de desconexión (sk); } if (outq.size () == 0) {sk.interestops (selectionKey.op_read); }} private void doread (selectionkey sk) {// todo método auto generado stub Socketchannel canal = (Socketchannel) sk.channel (); Bytebuffer bb = bytebuffer.allocate (8192); int len; intente {len = canal.read (bb); if (len <0) {desconectar (sk); devolver; }} Catch (Exception e) {// tODO: manejar excepción de desconexión (sk); devolver; } bb.flip (); tp.execute (nuevo HandlemSg (SK, BB)); } private void desconectado (selectionkey sk) {// toDo método generado automáticamente stub // omitir la operación de cierre en seco} private void doacept (selectionkey sk) {// todo método autogenerado con múltiples servidores de spersocketchannel = (ServerChetchannel) sk.channel (); Socketchannel ClientChannel; intente {ClientChannel = server.accept (); ClientChannel.ConfigureBlocking (falso); SelectionKey ClientKey = ClientChannel.Register (selector, selectionKey.op_read); ECHOCLIENT ECHOCLINET = new Echoclient (); ClientKey.attach (ECHOCLINET); InetAddress clientAddress = clientChannel.socket (). GetInetAddress (); System.out.println ("Conexión aceptada de" + ClientAddress.gethostaddress ()); } catch (Exception e) {// tODO: manejar excepción}} public static void main (string [] args) {// tODO método generado automático stub multithithreadnioechoserver echoserver = new multithithreadnioechoserver (); intente {echoserver.startserver (); } Catch (Exception e) {// tODO: manejar excepción}}}El código es solo para referencia, y su característica principal es que está interesado en diferentes eventos para hacer cosas diferentes.
Cuando se usa el cliente retrasado que se simuló anteriormente, el consumo de tiempo esta vez es entre 2 ms y 11 ms. La mejora del rendimiento es obvia.
Resumir:
1. Después de que NIO prepare los datos, los entregará a la aplicación de procesamiento. El proceso de lectura/escritura de datos todavía se completa en el hilo de la aplicación, y solo despegará el tiempo de espera a un hilo separado.
2. Guarde el tiempo de preparación de datos (porque se puede reutilizar el selector)
5. AIO
Características de AIO:
1. Notificarme después de leer
2. IO no se acelerará, sino que se le notificará después de leerlo
3. Use funciones de devolución de llamada para realizar el procesamiento de negocios
Código relacionado con AIO:
AsynchronousServersocketchannel
servidor = asynchronousServerSocketchannel.open (). bind (nuevo inetSocketAddress (puerto));
Use el método de aceptar en el servidor
Resumen público <a> void acepta (un archivo adjunto, finalización de finalización <asynchronoussocketchannel,? Super a> manejador);
FinalationHandler es una interfaz de devolución de llamada. Cuando hay un cliente que acepte, hace lo que está en el controlador.
Código de muestra:
server.accept (nulo, nuevo finalización de finalización <asynchronoussocketchannel, object> () {final bytebuffer buffer = byteBuffer.allocate (1024); public void completado (asynChonousSocketchEntEnt Resultado, accesorio de objetos) {System.out.println (hilo.CurrentThread (). GetName (); {buffer.clear (); servidor.accept (nulo, this);Aquí usamos el futuro para lograr un retorno instantáneo. Para el futuro, consulte el artículo anterior
Basado en la comprensión de NIO, mirando AIO, la diferencia es que AIO espera que el proceso de lectura y escritura llame a la función de devolución de llamada antes de completar.
Nio es sincrónico y sin bloqueo
AIO es asíncrono y no bloqueo
Dado que el proceso de lectura y escritura de NIO todavía se completa en el hilo de la aplicación, NIO no es adecuado para aquellos con un proceso de lectura y escritura durante mucho tiempo.
El proceso de lectura y escritura de AIO se notifica solo después de que se complete, por lo que AIO es competente para las tareas de proceso de lectura y escritura de peso pesado y a largo plazo.