IO tidak merasa terlalu terkait dengan multi-threading, tetapi NIO telah mengubah cara utas digunakan pada tingkat aplikasi dan memecahkan beberapa kesulitan praktis. AIO adalah IO asinkron dan seri sebelumnya juga terkait. Di sini, untuk belajar dan merekam, saya juga menulis sebuah artikel untuk memperkenalkan Nio dan AIO.
1. Apa itu Nio
NIO adalah singkatan dari I/O baru, yang berlawanan dengan metode I/O berbasis aliran lama. Dilihat dari nama, itu mewakili satu set baru standar I/O Java. Itu dimasukkan ke dalam JDK di Java 1.4 dan memiliki fitur berikut:
Semua operasi baca dan tulis dari saluran harus melalui buffer, dan saluran adalah abstraksi IO, dan ujung saluran lainnya adalah file yang dimanipulasi.
2. Buffer
Implementasi Buffer di Java. Jenis data dasar memiliki buffer yang sesuai
Contoh sederhana menggunakan buffer:
tes paket; impor java.io.file; impor java.io.fileinputStream; impor java.nio.bytebuffer; import java.nio.channels.filechannel; tes kelas publik {public static void main (string [] args) melempar pengecualian {fileInputStream fin = FileInputStream baru (file baru ("d: //temp_buffer.tmp")); Filechannel fc = fin.getchannel (); Bytebuffer bytebuffer = bytebuffer.allocate (1024); fc.read (bytebuffer); fc.close (); bytebuffer.flip (); // baca dan tulis konversi}}Langkah -langkah untuk digunakan dirangkum:
1. Dapatkan saluran
2. Ajukan Buffer
3. Buat hubungan baca/tulis antara saluran dan buffer
4. Tutup
Contoh berikut adalah menggunakan NIO untuk menyalin file:
public static void nioCopyFile (String Resource, String Destination) melempar IoException {fileInputStream fis = FileInputStream baru (sumber daya); FileOutputStream fos = new FileOutputStream (tujuan); Filechannel readchannel = fis.getchannel (); // Baca File Channel FileChannel Writechannel = fos.getChannel (); // Tulis File Channel ByteBuffer buffer = byteBuffer.allocate (1024); // baca di cache data while (true) {buffer.clear (); int len = readchannel.read (buffer); // Baca dalam data if (len == -1) {break; // Baca di} buffer.flip (); writechannel.write (buffer); // tulis ke file} readchannel.close (); writechannel.close (); }Ada 3 parameter penting dalam buffer: posisi, kapasitas dan batas
Di sini kita perlu membedakan antara kapasitas dan batas atas. Misalnya, jika buffer memiliki 10kb, maka 10kb adalah kapasitasnya. Jika saya membaca file 5KB ke dalam buffer, maka batas atas adalah 5kb.
Berikut adalah contoh untuk memahami 3 parameter penting ini:
public static void main (string [] args) melempar Exception {byteBuffer b = byteBuffer.allocate (15); // 15-byte buffer system.out.println ("limit =" + b.limit () + "kapasitas =" + b.capacity () + "position =" + b.position ()); untuk (int i = 0; i <10; i ++) {// simpan 10 byte data b.put ((byte) i); } System.out.println ("Limit =" + B.Limit () + "CAPASICY =" + B.Capacity () + "Posisi =" + B.Posisi ()); b.flip (); // Reset posisi System.out.println ("Limit =" + B.Limit () + "CAPASY =" + B.Capacity () + "Posisi =" + B.Posisi ()); untuk (int i = 0; i <5; i ++) {System.out.print (b.get ()); } System.out.println (); System.out.println ("Limit =" + B.Limit () + "CAPASY =" + B.Capacity () + "Posisi =" + B.Posisi ()); b.flip (); System.out.println ("Limit =" + B.Limit () + "CAPASY =" + B.Capacity () + "Posisi =" + B.Posisi ()); }Seluruh proses ditunjukkan pada gambar:
Pada saat ini, posisinya adalah dari 0 hingga 10, dan kapasitas dan batas tetap tidak berubah.
Operasi ini mengatur ulang posisi. Biasanya, saat mengonversi buffer dari mode tulis ke mode membaca, Anda perlu melakukan metode ini. Operasi flip () tidak hanya mengatur ulang posisi saat ini ke 0, tetapi juga menetapkan batas ke posisi posisi saat ini.
Arti batasnya adalah untuk menentukan data mana yang bermakna. Dengan kata lain, data dari posisi ke batas adalah data yang bermakna karena merupakan data dari operasi terakhir. Oleh karena itu, operasi flip sering berarti membaca dan menulis konversi.
Makna yang sama seperti di atas.
Sebagian besar metode dalam buffer mengubah 3 parameter ini untuk mencapai fungsi tertentu:
Public Final Buffer Rewind ()
Atur posisi nol dan jelas tanda
Buffer akhir publik Clear ()
Atur posisi nol, dan atur batas ke ukuran kapasitas dan bersihkan tanda bendera
flip buffer akhir publik ()
Batas set pertama ke posisi di mana posisi berada, lalu setel posisi ke nol, dan hapus tanda bit flag, biasanya digunakan selama konversi baca dan tulis
File dipetakan ke memori
public static void main (string [] args) melempar Exception {randomAccessFile raf = new randomAccessFile ("c: //mapfile.txt", "rw"); Filechannel fc = raf.getchannel (); // Peta file ke dalam memori 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); // Ubah file raf.close (); }Memodifikasi MappedByTeBuffer setara dengan memodifikasi file itu sendiri, sehingga kecepatan operasinya sangat cepat.
3. Saluran
Struktur Umum Server Jaringan Multi-Threaded:
Server multi-threaded sederhana:
public static void main (string [] args) melempar Exception {serversocket echoserver = null; Socket ClientSocket = null; coba {echoServer = server baru (8000); } catch (ioException e) {System.out.println (e); } while (true) {coba {clientocket = echoServer.accept (); System.out.println (clientocket.getRemoteSocketAddress () + "Connect!"); tp.execute (handlemsg baru (clientsocket)); } catch (ioException e) {System.out.println (e); }}}Fungsinya adalah untuk menulis kembali data ke klien setiap kali server membaca.
Di sini TP adalah kumpulan utas, dan Handlemsg adalah kelas yang menangani pesan.
kelas statis handlemsg mengimplementasikan runnable {oMit bagian informasi public void run () {try {is = new BufferedReader (new InputStreamReader (clientocket.getInputStream ())); os = printwriter baru (clientocket.getoutputStream (), true); // Baca data yang dikirim oleh klien dari inputStream String inputline = null; long b = system.currentTimemillis (); while ((inputline = is.readline ())! = null) {os.println (inputline); } panjang e = sistem. currentTimemillis (); Sistem. out.println ("bekas:"+(e - b)+"ms"); } catch (ioException e) {e.printstacktrace (); } akhirnya {tutup sumber daya}}}Klien:
public static void main (string [] args) melempar pengecualian {socket client = null; Printwriter writer = null; BufferedReader Reader = null; coba {klien = soket baru (); client.connect (inetsocketAddress baru ("localhost", 8000)); penulis = printwriter baru (client.getoutputStream (), true); writer.println ("halo!"); writer.flush (); pembaca = BufferedReader baru (inputStreamReader baru (client.getInputStream ())); System.out.println ("Dari server:" + reader.readline ()); } catch (Exception e) {} akhirnya {// hilangkan Penutupan Sumber Daya}}Pemrograman jaringan di atas sangat mendasar, dan akan ada beberapa masalah menggunakan metode ini:
Gunakan satu utas untuk setiap klien. Jika klien mengalami pengecualian seperti penundaan, utas dapat ditempati untuk waktu yang lama. Karena persiapan dan pembacaan data ada di utas ini. Pada saat ini, jika ada banyak klien, itu dapat mengkonsumsi banyak sumber daya sistem.
Larutan:
Gunakan non-blocking nio (baca data tanpa menunggu, data siap sebelum bekerja)
Untuk mencerminkan efisiensi penggunaan NIO.
Di sini kami pertama -tama mensimulasikan klien yang tidak efisien untuk mensimulasikan keterlambatan karena jaringan:
Private Static ExecutorService TP = Executors.NewCachedThreadPool (); private static int int sleep_time = 1000*1000*1000; kelas public static echoclient mengimplementasikan runnable {public void run () {try {client = new socket (); client.connect (inetsocketAddress baru ("localhost", 8000)); penulis = printwriter baru (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 (Exception e) {}}}Output sisi server:
Habiskan: 6000ms
Habiskan: 6000ms
Habiskan: 6000ms
Habiskan: 6001ms
Habiskan: 6002ms
Habiskan: 6002ms
Habiskan: 6002ms
Habiskan: 6002ms
Habiskan: 6003ms
Habiskan: 6003ms
Karena
while ((inputline = is.readline ())! = null)
Itu diblokir, jadi waktu dihabiskan untuk menunggu.
Apa yang akan terjadi jika Nio terbiasa menangani masalah ini?
Salah satu fitur besar NIO adalah: beri tahu saya jika data sudah siap
Saluran sedikit mirip dengan stream, dan saluran dapat sesuai dengan file atau soket jaringan.
Pemilih adalah pemilih yang dapat memilih saluran dan melakukan sesuatu.
Utas dapat sesuai dengan pemilih, dan pemilih dapat polling beberapa saluran, dan setiap saluran memiliki soket.
Dibandingkan dengan utas di atas yang sesuai dengan soket, setelah menggunakan NIO, utas dapat polling beberapa soket.
Ketika pemilih panggilan pilih (), ia akan memeriksa apakah ada klien yang telah menyiapkan data. Ketika tidak ada data yang siap, pilih () akan memblokir. Biasanya dikatakan bahwa NIO tidak memblokir, tetapi jika data belum siap, masih akan ada pemblokiran.
Saat data siap, setelah menelepon SELECT (), seleksi akan dikembalikan. SelectionKey menunjukkan bahwa data saluran pada pemilih telah disiapkan.
Saluran ini hanya akan dipilih saat data siap.
Dengan cara ini, NIO mengimplementasikan utas untuk memantau banyak klien.
Klien dengan penundaan jaringan yang baru saja disimulasikan tidak akan mempengaruhi utas di bawah NIO, karena ketika jaringan soket menunda, data tidak siap, dan pemilih tidak akan memilihnya, tetapi akan memilih klien lain yang siap.
Perbedaan antara SelectNow () dan Select () adalah bahwa SelectNow () tidak memblokir. Ketika tidak ada klien menyiapkan data, SelectNow () tidak akan memblokir dan akan mengembalikan 0. Ketika klien menyiapkan data, selectNow () mengembalikan jumlah klien yang disiapkan.
Kode utama:
tes paket; impor java.net.inetaddress; impor java.net.inetsocketaddress; impor java.net.socket; impor java.nio.channels.selectionKey; impor java.nio.channels.selector; import java.nio.channels.selektor; impor java.nio.nio. java.nio.channels.socketchannel; impor java.nio.channels.spi.abstractselector; impor java.nio.channels.spi.selectorprovider; import java.util.hashmap; impor java.util.iterator; impor java.util.linkeder; java.util.set; impor java.util.concurrent.executorservice; import java.util.concurrent.executors; kelas publik multithreadnioechoserver {peta statis public <socket, long> geym_time_stat = hashmap baru <socket, long> (); kelas echoclient {private linkedList <byteBuffer> outq; EchoClient () {outq = new LinkedList <byteBuffer> (); } public LinkedList <ByTeBuffer> getOutputQueue () {return outq; } public void enqueue (bytebuffer bb) {outq.addfirst (bb); }} class handlemsg mengimplementasikan runnable {selectionKey SK; BYTEBUFFER BB; handlemsg publik (selectionKey SK, bytebuffer bb) {super (); this.sk = sk; this.bb = bb; } @Override public void run () {// TODO Metode yang dihasilkan otomatis Stub echoclient echoclient = (echoclient) sk.attachment (); echoclient.enqueue (BB); sk.Interestops (selectionKey.op_read | selectionkey.op_write); selector.wakeup (); }} pemilih pemilih pribadi; ExecutorService TP = Executors.NewCachedThreadPool (); private void starterver () melempar pengecualian {selector = selectorprovider.provider (). OpenSelector (); ServerSocketchannel ssc = serversocketchannel.open (); ssc.configureblocking (false); Inetsocketaddress Isa = inetsocketAddress baru (8000); ssc.socket (). bind (Isa); // Daftarkan peristiwa yang menarik, di sini tertarik pada Accpet Event SectionKey AcceptKey = SSC.Register (Selector, selectionkey.op_accept); untuk (;;) {selector.select (); Seteedkeys = 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 ((socketnel) 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 ("pengeluaran:" + (e - b) + "ms"); }}}} private void dowrite (selectionKey SK) {// TODO Metode yang dihasilkan otomatis Stubchetchannel channel = (socketchannel) sk.channel (); Echoclient echoclient = (echoclient) sk.Attachment (); LinkedList <ByTeBuffer> outq = echoclient.getOutputQueue (); Bytebuffer bb = outq.getLast (); coba {int len = channel.write (bb); if (len == -1) {Disconnect (SK); kembali; } if (bb.remaining () == 0) {outq.removelast (); }} catch (Exception e) {// todo: handle Exception Disconnect (SK); } if (outq.size () == 0) {sk.Interestops (selectionKey.op_read); }} private void doread (selectionKey SK) {// todo Metode yang dihasilkan otomatis Stub Socketchannel channel = (socketchannel) sk.channel (); Bytebuffer bb = bytebuffer.allocate (8192); int len; coba {len = channel.read (bb); if (len <0) {Disconnect (SK); kembali; }} catch (Exception e) {// todo: handle Exception Disconnect (SK); kembali; } bb.flip (); tp.execute (handlemsg baru (SK, BB)); } Private void Disconnect (selectionKey SK) {// TODO Metode yang dihasilkan secara otomatis Stub // menghilangkan operasi penutupan kering} private void doaccept (selectionKey SK) {// TODO Metode yang dihasilkan secara otomatis Stub server servernel server = (serverSocketchannel) sk.channel (); Socketchannel Clientchannel; coba {clientchannel = server.accept (); clientchannel.configureblocking (false); SelectionKey clientkey = clientchannel.register (selector, selectionkey.op_read); Echoclient echoclinet = echoclient baru (); clientkey.attach (echoclinet); Inetaddress clientAddress = clientchannel.socket (). GetInetAddress (); System.out.println ("Koneksi yang Diterima dari" + ClientAddress.GetHostAddress ()); } catch (Exception e) {// TODO: Handle Exception}} public static void main (string [] args) {// TODO METODE AUTO-AUTO-ENCOYATED Stub MULTITHREADNIOECHOSERVER ECHOSERVER = MULTITHREADNIOECHERERVER () baru; coba {echoserver.startserver (); } catch (exception e) {// todo: handle exception}}}Kode ini hanya untuk referensi, dan fitur utamanya adalah Anda tertarik pada berbagai acara untuk melakukan hal -hal yang berbeda.
Saat menggunakan klien tertunda yang disimulasikan sebelumnya, konsumsi waktu kali ini adalah antara 2ms dan 11ms. Peningkatan kinerja jelas.
Meringkaskan:
1. Setelah NIO menyiapkan data, itu akan menyerahkannya ke aplikasi untuk diproses. Proses pembacaan/penulisan data masih selesai di utas aplikasi, dan hanya akan menghapus waktu tunggu ke utas terpisah.
2. Simpan waktu persiapan data (karena pemilih dapat digunakan kembali)
5. aio
Fitur AIO:
1. Beri tahu saya setelah membaca
2. IO tidak akan dipercepat, tetapi akan diberitahu setelah membacanya
3. Gunakan fungsi panggilan balik untuk melakukan pemrosesan bisnis
Kode terkait AIO:
AsynchronousServerCocketchannel
server = asynchronServersocketchannel.open (). bind (inetsocketAddress (port));
Gunakan metode penerimaan di server
abstrak publik <a> void menerima (lampiran, penyelesaian handler <asynchronoussocketchannel,? Super a> handler);
CompletionHandler adalah antarmuka panggilan balik. Ketika ada klien menerima, itu melakukan apa yang ada di pawang.
Kode contoh:
Server.accept (null, New CompletionHandler <asynchronoussocketchannel, Object> () {final bytebuffer buffer = bytebuffer.allocate (1024); void publik selesai (asynchronoussocketchannel, lampiran objek) {System.out.println (thread.currentrread = nol (). {Buffer.clear (); server.accept (null, this);Di sini kita menggunakan masa depan untuk mencapai pengembalian instan. Untuk masa depan, silakan merujuk ke artikel sebelumnya
Berdasarkan pemahaman NIO, melihat AIO, perbedaannya adalah bahwa AIO menunggu proses baca dan tulis untuk memanggil fungsi callback sebelum diselesaikan.
Nio sinkron dan tidak blokir
Aio asinkron dan tidak blokir
Karena proses membaca dan menulis NIO masih selesai di utas aplikasi, NIO tidak cocok untuk mereka yang memiliki proses membaca dan menulis yang lama.
Proses baca dan tulis AIO diberitahu hanya setelah selesai, jadi AIO kompeten untuk tugas-tugas proses membaca dan menulis jangka panjang dan jangka panjang.