IO ne se sent pas beaucoup lié au multi-threading, mais NIO a changé la façon dont les threads sont utilisés au niveau de l'application et résolu certaines difficultés pratiques. AIO est ASynchrone IO et la série précédente est également liée. Ici, pour l'apprentissage et l'enregistrement, j'ai également écrit un article pour présenter Nio et AIO.
1. Qu'est-ce que Nio
Nio est l'abréviation de nouvelles E / S, qui est opposée à l'ancienne méthode d'E / S basée sur des cours d'eau. À en juger par le nom, il représente un nouvel ensemble de normes d'E / S Java. Il a été incorporé dans le JDK dans Java 1.4 et possède les caractéristiques suivantes:
Toutes les opérations de lecture et d'écriture de la chaîne doivent passer par un tampon, et le canal est l'abstraction de IO, et l'autre extrémité du canal est le fichier manipulé.
2. Tampon
Implémentation du tampon en Java. Les types de données de base ont leurs tampons correspondants
Exemples simples d'utilisation de tampon:
test de package; 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) lève une exception {fileInputStream fin = new FileInputStream (new File ("d: //temp_buffer.tmp")); FileChannel fc = fin.getChannel (); ByteBuffer byteBuffer = byteBuffer.Allocation (1024); fc.read (bytebuffer); fc.close (); bytebuffer.flip (); // lire et écrire la conversion}}Les étapes à utiliser sont résumées:
1. Obtenez le canal
2. Demandez un tampon
3. Établir une relation de lecture / écriture entre le canal et le tampon
4. Fermer
L'exemple suivant est d'utiliser NIO pour copier des fichiers:
public static void niocopyfile (chaîne de ressources, destination de chaîne) lève ioException {fileInputStream fis = new FileInputStream (ressource); FileOutputStream fos = new FileOutputStream (destination); FileChannel Readchannel = fis.getChannel (); // Lire le canal de fichier fileChannel WriteChannel = fos.getChannel (); // Écrivez le canal de fichier ByteBuffer Buffer = ByteBuffer.Allocation (1024); // Lire dans le cache de données while (true) {buffer.clear (); int len = readchannel.read (tampon); // Lire dans les données if (len == -1) {Break; // Lire dans le} tampon.flip (); writeChannel.write (tampon); // Écrivez dans le fichier} readchannel.close (); writeChannel.close (); }Il y a 3 paramètres importants dans le tampon: position, capacité et limite
Ici, nous devons faire la distinction entre la capacité et la limite supérieure. Par exemple, si un tampon a 10 Ko, alors 10 Ko est la capacité. Si je lis le fichier 5KB dans le tampon, la limite supérieure est de 5 Ko.
Voici un exemple pour comprendre ces 3 paramètres importants:
public static void main (String [] args) lève une exception {byteBuffer b = byteBuffer.AllOcy (15); // 15-byte buffer System.out.println ("limit =" + b.limit () + "Capacité =" + B.Capacity () + "position =" + b.position ()); pour (int i = 0; i <10; i ++) {// Enregistrer 10 octets de données b.put ((octet) i); } System.out.println ("limit =" + b.limit () + "Capacité =" + B.Capacity () + "position =" + B.Position ()); b.flip (); // Réinitialiser le système de position.out.println ("limit =" + b.limit () + "capacité =" + b.capacity () + "position =" + b. position ()); pour (int i = 0; i <5; i ++) {System.out.print (b.get ()); } System.out.println (); System.out.println ("limit =" + b.limit () + "Capacité =" + B.Capacity () + "position =" + b. position ()); b.flip (); System.out.println ("limit =" + b.limit () + "Capacité =" + B.Capacity () + "position =" + b. position ()); }L'ensemble du processus est illustré sur la figure:
À l'heure actuelle, la position est de 0 à 10 et la capacité et la limite restent inchangées.
Cette opération réinitialise la position. Habituellement, lors de la conversion du tampon du mode d'écriture en mode de lecture, vous devez effectuer cette méthode. L'opération Flip () réinitialise non seulement la position actuelle à 0, mais définit également la limite à la position de la position actuelle.
Le sens de la limite est de déterminer quelles données sont significatives. En d'autres termes, les données de position à limite sont des données significatives car ce sont les données de la dernière opération. Par conséquent, les opérations de retournement signifient souvent la conversion de lecture et d'écriture.
La même signification que ci-dessus.
La plupart des méthodes de tampon modifient ces 3 paramètres pour atteindre certaines fonctions:
tampon final public Rewind ()
Définir la position zéro et claire marque
tampon final public clear ()
Réglez la position zéro et définissez la limite sur la taille de la capacité et effacez la marque de drapeau
tampon final public flip ()
Définissez d'abord la limite de la position où se trouve la position, puis définissez la position à zéro, et effacez la marque de bit de drapeau, généralement utilisée pendant la conversion de lecture et d'écriture
Fichier mappé à la mémoire
public static void main (String [] args) lève une exception {randomaccessfile raf = new randomaccessfile ("c: //mapfile.txt", "rw"); FileChannel fc = raf.getChannel (); // mappe le fichier dans la mémoire mappée parByteBuffer mbb = fc.map (filechannel.mapmode.read_write, 0, raf.length ()); while (mbb.hasreMinging ()) {System.out.print ((char) mbb.get ()); } mbb.put (0, (octet) 98); // modifie le fichier raf.close (); }La modification du mappé de bystebuffer équivaut à modifier le fichier lui-même, donc la vitesse de fonctionnement est très rapide.
3. Channel
Structure générale du serveur réseau multithread:
Serveur multi-thread simple:
public static void main (String [] args) lève une exception {Serversocket eChoServer = null; Socket ClivitySocket = NULL; try {eChoServer = new Serversocket (8000); } catch (ioException e) {System.out.println (e); } while (true) {try {clientSocket = eChoServer.Accept (); System.out.println (clientSocket.getRemotesocketAddress () + "Connect!"); TP.ExECUTE (New HandleMsg (ClientSocket)); } catch (ioException e) {System.out.println (e); }}}La fonction consiste à réécrire les données au client chaque fois que le serveur se lit.
Ici, TP est un pool de threads, et Handlemsg est la classe qui gère les messages.
statique class handlemsg implémente runnable {omit partie de l'information publique void run () {try {is = new BuffereDaDer (new InputStreamReader (clientSocket.getInputStream ())); OS = new PrintWriter (clientSocket.getOutputStream (), true); // Lisez les données envoyées par le client à partir de la chaîne InputStream inputLine = null; long b = System.currenttimemillis (); while ((inputline = is.readline ())! = null) {os.println (inputline); } long e = système. CurrentTimemillis (); Système. out.println ("dépenser:" + (e - b) + "ms"); } catch (ioException e) {e.printStackTrace (); } Enfin {Ressource fermer}}}Client:
public static void main (String [] args) lève une exception {socket client = null; PrintWriter Writer = NULL; BufferedReader Reader = NULL; essayez {client = new socket (); client.connect (new InetsocketAddress ("localhost", 8000)); writer = new printwriter (client.getOutputStream (), true); écrivain.println ("Hello!"); écrivain.flush (); Reader = new BufferedReader (new InputStreamReader (client.getInputStream ())); System.out.println ("From Server:" + Reader.Readline ()); } catch (exception e) {} enfin {// omettre la fermeture des ressources}}La programmation réseau ci-dessus est très basique, et il y aura des problèmes en utilisant cette méthode:
Utilisez un thread pour chaque client. Si le client subit une exception telle que le retard, le fil peut être occupé pendant longtemps. Parce que la préparation et la lecture des données sont dans ce fil. Pour le moment, s'il y a de nombreux clients, il peut consommer beaucoup de ressources système.
Solution:
Utilisez le NIO non bloquant (lisez les données sans attendre, les données sont prêtes avant de travailler)
Afin de refléter l'efficacité de l'utilisation de NIO.
Ici, nous simulons d'abord un client inefficace pour simuler le retard dû au réseau:
Private Static ExecutorService TP = exécuteurs.NewCachedThreadPool (); Final statique privé int sleep_time = 1000 * 1000 * 1000; Classe statique publique Echoclient implémente Runnable {public void run () {try {client = new socket (); client.connect (new InetsocketAddress ("localhost", 8000)); writer = new printwriter (client.getOutputStream (), true); écrivain.print ("h"); Locksupport.parknanos (sleep_time); écrivain.print ("e"); Locksupport.parknanos (sleep_time); écrivain.print ("L"); Locksupport.parknanos (sleep_time); écrivain.print ("L"); Locksupport.parknanos (sleep_time); écrivain.print ("o"); Locksupport.parknanos (sleep_time); écrivain.print ("!"); Locksupport.parknanos (sleep_time); écrivain.println (); écrivain.flush (); } catch (exception e) {}}}Sortie côté serveur:
dépenses: 6000 ms
dépenses: 6000 ms
dépenses: 6000 ms
dépenses: 6001 ms
dépenses: 6002 ms
dépenses: 6002 ms
dépenses: 6002 ms
dépenses: 6002 ms
dépenses: 6003 ms
dépenses: 6003 ms
parce que
while ((inputline = is.readline ())! = null)
Il est bloqué, donc le temps est passé à attendre.
Que se passerait-il si Nio était utilisé pour faire face à ce problème?
L'une des grandes caractéristiques de NIO est: informez-moi si les données sont prêtes
Le canal est un peu similaire aux flux et un canal peut correspondre à des fichiers ou des prises de réseau.
Le sélecteur est un sélecteur qui peut sélectionner un canal et faire quelque chose.
Un fil peut correspondre à un sélecteur et un sélecteur peut interroger plusieurs canaux, et chaque canal a une prise.
Par rapport au thread ci-dessus correspondant à une prise, après avoir utilisé NIO, un thread peut interroger plusieurs prises.
Lorsque le sélecteur appelle SELECT (), il vérifiera si un client a préparé les données. Lorsqu'aucune donnée n'est prête, sélectionnez () bloquera. On dit généralement que Nio n'est pas bloquant, mais si les données ne sont pas prêtes, il y aura toujours un blocage.
Lorsque les données sont prêtes, après avoir appelé SELECT (), un SELECTIONKEY sera renvoyé. Le SelectionKey indique que les données d'un canal sur un sélecteur ont été préparées.
Ce canal ne sera sélectionné que lorsque les données seront prêtes.
De cette façon, NIO implémente un fil pour surveiller plusieurs clients.
Le client avec le retard du réseau juste simulé n'affectera pas les threads sous NIO, car lorsqu'un réseau de socket retarde, les données ne sont pas prêtes et le sélecteur ne le sélectionnera pas, mais sélectionnera d'autres clients préparés.
La différence entre selectNow () et Select () est que selectNow () ne bloque pas. Lorsqu'aucun client ne prépare les données, selectNow () ne bloquera pas et reviendra 0. Lorsqu'un client prépare des données, selectNow () renvoie le nombre de clients préparés.
Code principal:
test de package; Importer java.net.inetAddress; import java.net.inetsocketaddress; import java.net.socket; import java.nio.channels.selectionkey; import java.nio.channels.select java.nio.channels.socketchannel; import java.nio.channels.spi.abstractSelector; import java.nio.channels.spi.selectorprovider; import java.util.linkudlist; import Java.util.iterator; import Java.Sset; java.util.concurrent.executorService; import java.util.concurrent.executors; classe publique MultithreadnioEchoServer {public static map <socket, long> geym_time_stat = new hashmap <socket, long> (); classe ECHOCLIENT {private LinkedList <ByteBuffer> OutQ; EChoClient () {outq = new LinkedList <ByTeBuffer> (); } public LinkedList <yteBuffer> getOutputQueue () {return outq; } public void enqueue (bytebuffer bb) {OutQ.AddFirst (bb); }} class handlemsg implémente runnable {selectionKey sk; Bytebuffer BB; public handlemsg (SELECTIONKEKEK SK, ByteBuffer bb) {super (); this.sk = sk; this.bb = bb; } @Override public void run () {// TODO Méthode générée automatique Stume ECHOCLIENT ECHOCLIENT = (ECHOCLIENT) SK.Attachment (); echoclient.enqueue (BB); sk.interestops (selectionkey.op_read | selectionkey.op_write); selector.wakeup (); }} sélecteur privé sélecteur; Private ExecutorService tp = exécutors.newcachedThreadPool (); private void starterServer () lève une exception {sélector = selectorProvider.Provider (). Openselector (); SERVERSACHETHETCHANNEL SSC = SERVERSOCHAUX.Open (); ssc.ConfigureBlocking (false); InetsocketAddress ISA = new IneTSocketAddress (8000); ssc.socket (). bind (Isa); // Enregistrer l'événement d'intérêt, ici est intéressé par l'ACCPET Event SelectionKey AcceptKey = SSC.Register (Selector, SelectionKey.op_Accept); pour (;;) {Selector.Select (); Set ReadyKeys = Selector.SelectedKeys (); Iterator i = readykeys.iterator (); long e = 0; while (i.hasnext ()) {selectionKey sk = (selectionKey) i.next (); I.Remove (); if (sk.isacceptable ()) {doaccept (sk); } else if (sk.isvalid () && sk.isreadable ()) {if (! geym_time_stat.containskey (((socketchannel) sk .channel ()). socket ())) {geym_time_stat.put ((socketchannel) sk.channel ()). } doread (sk); } else if (sk.isvalid () && sk.iswitable ()) {dowrite (sk); e = System.Currenttimemillis (); long b = geym_time_stat.remove (((socketchannel) sk .channel ()). socket ()); System.out.println ("dépenser:" + (e - b) + "ms"); }}}} private void dowrite (SELECTIONKEKEK SK) {// TODO Méthode générée automatique Stub Socketchannel Channel = (socketchannel) sk.channel (); Echoclient Echoclient = (échoclient) sk.attachment (); LinkedList <ByTeBuffer> OutQ = eChoclient.getOutputQueue (); Bytebuffer bb = outq.getLast (); try {int len = channel.write (bb); if (len == -1) {disonnect (sk); retour; } if (bb.reMinging () == 0) {outq.Removelast (); }} catch (exception e) {// todo: greg exception déconnect (sk); } if (outq.size () == 0) {sk.interestops (selectionKey.op_read); }} private void doread (SelectionKey Sk) {// TODO Méthode générée automatique Stub Socketchannel Channel = (socketchannel) sk.channel (); Bytebuffer bb = bytebuffer.Allocation (8192); int len; essayez {len = canal.read (bb); if (len <0) {disonnect (sk); retour; }} catch (exception e) {// todo: greg exception déconnect (sk); retour; } bb.flip (); TP.Execute (New Handlemsg (SK, BB)); } Private void Disconnect (SELECTIONKEKEK SK) {// TODO Méthode générée automatiquement Stume // Omettre l'opération de clôture à sec} private void Doaccept (SelectionKey Sk) {// TODO Méthode générée automatiquement Stub Serversocketchannel Server = (ServersockEtchannel) Sk.Channel (); Socketchannel ClientChannel; essayez {clientChannel = server.accept (); clientChannel.ConfigureBlocking (false); SELECTIONKEKEY ClientKey = ClientChannel.Register (Selector, SelectionKey.op_Read); Echoclient echoclinet = new echoclient (); ClientKey.Attach (Echoclinet); InetAddress ClientAddress = ClientChannel.Socket (). GetinetAddress (); System.out.println ("Connexion acceptée à partir de" + clientAddress.GethostAddress ()); } catch (exception e) {// todo: greg exception}} public static void main (string [] args) {// todo meto-généré par la méthode MultithreadnioEchoServer eChoServer = new MultithReadNioEchoServer (); essayez {echoServer.StartServer (); } catch (exception e) {// todo: gère l'exception}}}Le code est pour référence uniquement, et sa principale fonctionnalité est que vous êtes intéressé par différents événements pour faire des choses différentes.
Lorsque vous utilisez le client retardé qui a été simulé plus tôt, la consommation de temps cette fois se situe entre 2 ms et 11 ms. L'amélioration des performances est évidente.
Résumer:
1. Après que Nio ait préparé les données, il le remettra à l'application de traitement. Le processus de lecture / écriture des données est toujours terminé dans le thread de l'application, et il ne fera que déshabiller le temps d'attente à un thread séparé.
2. Enregistrer le temps de préparation des données (car le sélecteur peut être réutilisé)
5. AIO
Caractéristiques de l'AIO:
1. M'avis après avoir lu
2. Io ne sera pas accéléré, mais sera informé après l'avoir lu
3. Utilisez des fonctions de rappel pour effectuer un traitement commercial
Code lié à l'AIO:
AsynchroneServerSocketchannel
server = asynchronousServersocketchannel.open (). bind (new IneTSocketAddress (port));
Utilisez la méthode Accept sur le serveur
Abstract public <a> Void accepter (une pièce jointe, complétionnher <asynchronoussocketchannel,? Super a> Handler);
CompletionHandler est une interface de rappel. Lorsqu'il y a un client accepter, il fait ce qui est dans le gestionnaire.
Exemple de code:
Server.Accept (null, nouveau complétionhandler <asynchroneSocketchannel, objet> () {final bytebuffer tamper = bytebuffer.allocate (1024); public void terminé (AsynchronSockettchannel Result, objet attachement) {System.out.println (thread.currentThread (). {Buffer.Chear Server.Accept (Null);Ici, nous utilisons l'avenir pour obtenir un retour instantané. Pour l'avenir, veuillez vous référer à l'article précédent
Basé sur la compréhension de Nio, en regardant AIO, la différence est qu'AIO attend le processus de lecture et d'écriture pour appeler la fonction de rappel avant la fin.
Nio est synchrone et non bloquant
AIO est asynchrone et non bloquant
Étant donné que le processus de lecture et d'écriture NIO est toujours terminé dans le thread d'application, NIO ne convient pas à ceux qui ont un processus de lecture et d'écriture à long terme.
Le processus de lecture et d'écriture AIO n'est notifié qu'après sa fin, AIO est donc compétent pour les tâches de processus de lecture et d'écriture à long terme et à long terme.