Java NIO menyediakan cara kerja IO yang berbeda dari IO standar:
Saluran dan Buffer: IO standar beroperasi berdasarkan aliran byte dan aliran karakter, sedangkan NIO beroperasi berdasarkan saluran (Channel) dan buffer (Buffer). Data selalu dibaca dari saluran ke area buffer, atau ditulis dari buffer ke saluran.
IO Asinkron: Java NIO memungkinkan Anda menggunakan IO secara asinkron. Misalnya, saat thread membaca data dari saluran ke buffer, thread masih dapat melakukan hal lain. Ketika data ditulis ke buffer, thread dapat terus memprosesnya. Menulis ke saluran dari buffer serupa.
Penyeleksi: Java NIO memperkenalkan konsep penyeleksi, yang digunakan untuk mendengarkan peristiwa di berbagai saluran (misalnya: pembukaan koneksi, kedatangan data). Oleh karena itu, satu thread dapat mendengarkan beberapa saluran data.
Mari perkenalkan pengetahuan yang relevan tentang Java NIO secara detail.
Ikhtisar Java NIO
Java NIO terdiri dari bagian inti berikut:
Saluran
penyangga
Penyeleksi
Meskipun masih banyak kelas dan komponen lain di Java NIO, menurut saya Channel, Buffer, dan Selector merupakan API inti. Komponen lainnya, seperti Pipe dan FileLock, hanyalah kelas utilitas yang digunakan dengan tiga komponen inti. Oleh karena itu, dalam ulasan kali ini saya akan fokus pada ketiga komponen tersebut. Komponen lainnya dibahas dalam bab terpisah.
Saluran dan Penyangga
Pada dasarnya semua IO di NIO dimulai dari Channel. Saluran agak mirip dengan aliran. Data dapat dibaca dari Channel ke Buffer, atau ditulis dari Buffer ke Channel. Berikut ilustrasinya:
Ada beberapa jenis Saluran dan Buffer. Berikut implementasi beberapa Channel utama di JAVA NIO:
Saluran File
Saluran Datagram
Saluran Soket
ServerSocketChannel
Seperti yang Anda lihat, saluran ini mencakup IO jaringan UDP dan TCP, serta IO file.
Seiring dengan kelas-kelas ini ada beberapa antarmuka yang menarik, tapi demi kesederhanaan saya mencoba untuk tidak menyebutkannya dalam ikhtisar. Saya akan menjelaskannya di bab lain tutorial ini jika relevan.
Berikut ini adalah kunci implementasi Buffer di Java NIO:
ByteBuffer
CharBuffer
Penyangga Ganda
FloatBuffer
IntBuffer
Penyangga Panjang
Buffer Pendek
Buffer ini mencakup tipe data dasar yang dapat Anda kirim melalui IO: byte, short, int, long, float, double dan char.
Java NIO juga memiliki Mappyteuffer, yang digunakan untuk mewakili file yang dipetakan memori. Saya tidak akan menjelaskannya dalam ikhtisar.
Pemilih
Selector memungkinkan satu thread untuk menangani beberapa Saluran. Jika aplikasi Anda membuka beberapa koneksi (saluran), namun lalu lintas setiap koneksi sangat rendah, menggunakan Selector bisa menjadi hal yang nyaman. Misalnya saja di server obrolan.
Berikut ilustrasi penggunaan Selector untuk memproses 3 Channel dalam satu thread:
Untuk menggunakan Selector, Anda harus mendaftarkan Channel dengan Selector dan kemudian memanggil metode select()-nya. Metode ini akan memblokir hingga saluran terdaftar memiliki acara yang siap. Setelah metode ini kembali, thread dapat menangani kejadian ini. Contoh kejadian adalah koneksi baru yang masuk, penerimaan data, dll.
Java NIO vs IO
(Alamat asli bagian ini, penulis: Jakob Jenkov, penerjemah: Guo Lei, korektor: Fang Tengfei)
Setelah mempelajari Java NIO dan IO API, sebuah pertanyaan langsung muncul di benak saya:
Mengutip
Kapan saya harus menggunakan IO dan kapan saya harus menggunakan NIO? Pada artikel ini, saya akan mencoba menjelaskan dengan jelas perbedaan antara Java NIO dan IO, skenario penggunaannya, dan pengaruhnya terhadap desain kode Anda.
Perbedaan utama antara Java NIO dan IO
Tabel berikut merangkum perbedaan utama antara Java NIO dan IO. Perbedaan di setiap bagian tabel akan saya uraikan lebih detail.
IO NIO
Berorientasi aliran Berorientasi buffer
Memblokir IO Tidak memblokir IO
Penyeleksi
Berorientasi aliran dan berorientasi buffer
Perbedaan terbesar pertama antara Java NIO dan IO adalah IO berorientasi pada aliran dan NIO berorientasi pada buffer. Java IO berorientasi pada aliran, artinya satu atau lebih byte dibaca dari aliran pada satu waktu, dan hingga semua byte dibaca, byte tersebut tidak di-cache di mana pun. Selain itu, ia tidak dapat memindahkan data dalam aliran ke depan atau ke belakang. Jika Anda perlu memindahkan data yang dibaca dari aliran bolak-balik, Anda perlu menyimpannya dalam cache terlebih dahulu di buffer. Pendekatan berorientasi buffer Java NIO sedikit berbeda. Data dibaca ke dalam buffer yang kemudian diproses, dipindahkan bolak-balik di buffer sesuai kebutuhan. Hal ini meningkatkan fleksibilitas dalam pemrosesan. Namun, Anda juga perlu memeriksa apakah buffer berisi semua data yang perlu Anda proses. Selain itu, pastikan semakin banyak data yang dibaca ke dalam buffer, data yang belum diproses di buffer tidak ditimpa.
IO pemblokiran dan non-pemblokiran
Berbagai aliran Java IO diblokir. Artinya ketika thread memanggil read() atau write(), thread diblokir hingga beberapa data dibaca, atau data ditulis seluruhnya. Thread tidak dapat melakukan hal lain selama periode ini. Mode non-pemblokiran Java NIO memungkinkan thread mengirim permintaan untuk membaca data dari saluran tertentu, tetapi hanya dapat memperoleh data yang tersedia saat ini, maka tidak ada data yang diperoleh. Alih-alih membiarkan thread diblokir, thread dapat terus melakukan hal lain hingga data dapat dibaca. Hal yang sama berlaku untuk penulisan non-pemblokiran. Sebuah thread meminta untuk menulis beberapa data ke saluran, namun tidak perlu menunggu sampai data tersebut ditulis sepenuhnya. Sementara itu, thread dapat melakukan hal lain. Thread biasanya menggunakan waktu idle di IO non-pemblokiran untuk melakukan operasi IO di saluran lain, sehingga satu thread sekarang dapat mengelola beberapa saluran input dan output.
Penyeleksi
Selektor Java NIO memungkinkan satu thread untuk memonitor beberapa saluran input. Anda dapat mendaftarkan beberapa saluran menggunakan pemilih, dan kemudian menggunakan thread terpisah untuk "memilih" saluran: saluran ini sudah memiliki input yang dapat diproses siap untuk menulis. Mekanisme pemilihan ini memudahkan satu thread untuk mengelola banyak saluran.
Bagaimana NIO dan IO memengaruhi desain aplikasi
Baik Anda memilih kotak alat IO atau NIO, ada beberapa aspek yang dapat memengaruhi desain aplikasi Anda:
Panggilan API ke kelas NIO atau IO.
Pengolahan data.
Jumlah thread yang digunakan untuk memproses data.
panggilan API
Tentu saja, panggilan API saat menggunakan NIO terlihat berbeda dibandingkan saat menggunakan IO, tetapi hal ini tidak terduga karena alih-alih hanya membaca dari InputStream byte demi byte, data harus dibaca terlebih dahulu ke dalam buffer dan kemudian diproses.
Pengolahan data
Menggunakan desain NIO murni dibandingkan dengan desain IO, pemrosesan data juga terpengaruh.
Dalam desain IO, kita membaca data byte demi byte dari InputStream atau Reader. Misalkan Anda sedang memproses aliran data teks berbasis baris, misalnya:
Copy kode kodenya sebagai berikut:
Nama: Anna
Usia: 25
Surel: [email protected]
Telepon: 1234567890
Aliran baris teks dapat ditangani seperti ini:
Copy kode kodenya sebagai berikut:
InputStream input = … ; // ambil InputStream dari soket klien
Pembaca BufferedReader = BufferedReader baru (InputStreamReader baru (input));
String namaLine = reader.readLine();
String ageLine = pembaca.readLine();
String emailLine= pembaca.readLine();
String phoneLine= pembaca.readLine();
Perhatikan bahwa status pemrosesan ditentukan oleh berapa lama program telah dijalankan. Dengan kata lain, setelah metode reader.readLine() kembali, Anda tahu pasti bahwa baris teks telah dibaca. Inilah sebabnya readline() memblokir hingga seluruh baris telah dibaca. Anda juga mengetahui bahwa baris ini berisi nama; demikian pula, ketika panggilan readline() kedua kembali, Anda mengetahui bahwa baris ini berisi usia, dll. Seperti yang Anda lihat, pengendali ini hanya berjalan ketika data baru dibaca, dan mengetahui data apa yang ada di setiap langkah. Setelah thread yang sedang berjalan memproses beberapa data yang telah dibacanya, thread tersebut tidak akan mengembalikan data tersebut (kebanyakan). Gambar berikut juga menggambarkan prinsip ini:
Membaca data dari aliran yang diblokir
Meskipun implementasi NIO akan berbeda, berikut adalah contoh sederhananya:
Copy kode kodenya sebagai berikut:
Buffer ByteBuffer = ByteBuffer.alokasikan(48);
int bytesRead = inChannel.read(buffer);
Perhatikan baris kedua, membaca byte dari saluran ke ByteBuffer. Saat pemanggilan metode ini kembali, Anda tidak tahu apakah semua data yang Anda perlukan ada di buffer. Yang Anda tahu hanyalah buffer berisi beberapa byte, yang membuat pemrosesan agak sulit.
Misalkan setelah panggilan read(buffer) pertama, data yang dibaca ke buffer hanya setengah baris, misalnya "Nama: An", dapatkah Anda memproses data tersebut? Jelas tidak, Anda harus menunggu hingga seluruh baris data dibaca ke dalam cache Sebelum itu, pemrosesan data apa pun tidak ada artinya.
Jadi, bagaimana Anda tahu jika buffer berisi cukup data untuk diproses? Ya, kamu tidak tahu. Metode yang ditemukan hanya dapat melihat data di buffer. Hasilnya adalah Anda harus memeriksa data buffer beberapa kali sebelum Anda mengetahui bahwa semua data ada di buffer. Hal ini tidak hanya tidak efisien, tetapi juga dapat mengacaukan solusi pemrograman. Misalnya:
Copy kode kodenya sebagai berikut:
Buffer ByteBuffer = ByteBuffer.alokasikan(48);
int bytesRead = inChannel.read(buffer);
while(!bufferFull(byteBaca) ) {
bytesRead = inChannel.read(buffer);
}
Metode bufferFull() harus melacak berapa banyak data yang dibaca ke dalam buffer dan mengembalikan nilai benar atau salah, bergantung pada apakah buffer penuh. Dengan kata lain, jika buffer siap diproses berarti buffer sudah penuh.
Metode bufferFull() memindai buffer, namun harus tetap dalam keadaan yang sama seperti sebelum metode bufferFull() dipanggil. Jika tidak, data berikutnya yang dibaca ke dalam buffer mungkin tidak dibaca ke lokasi yang benar. Ini tidak mungkin, namun ini adalah masalah lain yang harus diwaspadai.
Jika buffer sudah penuh maka bisa diproses. Jika tidak berhasil, dan masuk akal dalam kasus Anda yang sebenarnya, Anda mungkin bisa menangani sebagian darinya. Namun dalam banyak kasus hal ini tidak terjadi. Gambar berikut menunjukkan "siklus data buffer siap":
Membaca data dari saluran sampai semua data dibaca ke dalam buffer
Meringkaskan
NIO memungkinkan Anda mengelola beberapa saluran (koneksi jaringan atau file) hanya dengan menggunakan satu thread (atau beberapa), namun kelemahannya adalah penguraian data bisa lebih rumit daripada membacanya dari aliran pemblokiran.
Jika Anda perlu mengelola ribuan koneksi yang dibuka secara bersamaan yang hanya mengirimkan sejumlah kecil data setiap kali, seperti server obrolan, server yang menerapkan NIO mungkin merupakan keuntungan. Demikian pula, jika Anda perlu memelihara banyak koneksi terbuka ke komputer lain, seperti di jaringan P2P, mungkin ada baiknya menggunakan thread terpisah untuk mengelola semua koneksi keluar Anda. Skema desain beberapa koneksi dalam satu thread ditunjukkan pada gambar di bawah ini:
Thread tunggal mengelola banyak koneksi
Jika Anda memiliki sejumlah kecil koneksi yang menggunakan bandwidth sangat tinggi, mengirimkan data dalam jumlah besar sekaligus, mungkin implementasi server IO biasa mungkin cocok. Gambar berikut mengilustrasikan desain server IO yang khas:
Desain server IO yang khas:
Koneksi ditangani oleh thread
Saluran
Saluran Java NIO mirip dengan stream, tetapi agak berbeda:
Data dapat dibaca dari saluran dan data dapat ditulis ke saluran. Namun aliran membaca dan menulis biasanya bersifat satu arah.
Saluran dapat dibaca dan ditulis secara asinkron.
Data dalam saluran terlebih dahulu harus dibaca dari Buffer, atau selalu ditulis dari Buffer.
Seperti disebutkan di atas, data dibaca dari saluran ke buffer dan data ditulis dari buffer ke saluran. Seperti yang ditunjukkan di bawah ini:
Implementasi saluran
Ini adalah implementasi saluran terpenting di Java NIO:
FileChannel: Membaca dan menulis data dari file.
DatagramChannel: dapat membaca dan menulis data dalam jaringan melalui UDP.
SocketChannel: Dapat membaca dan menulis data dalam jaringan melalui TCP.
ServerSocketChannel: Dapat memonitor koneksi TCP yang masuk, seperti server web. SocketChannel dibuat untuk setiap koneksi masuk baru.
Contoh Saluran Dasar
Berikut contoh penggunaan FileChannel untuk membaca data ke dalam Buffer:
Copy kode kodenya sebagai berikut:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel diChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.alokasikan(48);
int bytesRead = inChannel.read(buf);
while (byteBaca != -1) {
System.out.println("Baca " + byteBaca);
buf.flip();
while(buf.hasRemaining()){
Sistem.keluar.cetak((char) buf.get());
}
buf.hapus();
bytesRead = diChannel.read(buf);
}
aFile.close();
Perhatikan bahwa panggilan ke buf.flip() terlebih dahulu membaca data ke dalam Buffer, kemudian membalikkan Buffer, dan kemudian membaca data dari Buffer. Bagian selanjutnya akan membahas lebih detail tentang Buffer.
Penyangga
Buffer di Java NIO digunakan untuk berinteraksi dengan saluran NIO. Seperti yang Anda ketahui, data dibaca dari saluran ke buffer dan ditulis dari buffer ke saluran.
Buffer pada dasarnya adalah blok memori tempat data dapat ditulis dan kemudian data dapat dibaca. Memori ini dikemas sebagai objek NIO Buffer dan menyediakan serangkaian metode untuk mengakses memori ini dengan mudah.
Penggunaan dasar Buffer
Menggunakan Buffer untuk membaca dan menulis data umumnya mengikuti empat langkah berikut:
Tulis data ke Buffer
Panggil metode flip()
Membaca data dari Buffer
Panggil metode clear() atau metode compact()
Ketika data ditulis ke buffer, buffer mencatat berapa banyak data yang ditulis. Setelah Anda ingin membaca data, Anda perlu mengalihkan Buffer dari mode tulis ke mode baca melalui metode flip(). Dalam mode baca, semua data yang sebelumnya ditulis ke buffer dapat dibaca.
Setelah semua data dibaca, buffer perlu dibersihkan agar dapat ditulis kembali. Ada dua cara untuk menghapus buffer: memanggil metode clear() atau compact(). Metode clear() menghapus seluruh buffer. Metode compact() hanya akan menghapus data yang telah dibaca. Setiap data yang belum dibaca dipindahkan ke awal buffer, dan data yang baru ditulis ditempatkan setelah data yang belum dibaca di buffer.
Berikut adalah contoh penggunaan Buffer:
Copy kode kodenya sebagai berikut:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel diChannel = aFile.getChannel();
//membuat buffer dengan kapasitas 48 byte
ByteBuffer buf = ByteBuffer.alokasikan(48);
int bytesRead = inChannel.read(buf); //membaca ke dalam buffer.
while (byteBaca != -1) {
buf.flip();//siapkan buffer untuk dibaca
while(buf.hasRemaining()){
System.out.print((char) buf.get()); // membaca 1 byte sekaligus
}
buf.clear(); //siapkan buffer untuk menulis
bytesRead = diChannel.read(buf);
}
aFile.close();
Kapasitas, posisi dan batas buffer
Buffer pada dasarnya adalah blok memori tempat data dapat ditulis dan kemudian data dapat dibaca. Memori ini dikemas sebagai objek NIO Buffer dan menyediakan serangkaian metode untuk mengakses memori ini dengan mudah.
Untuk memahami cara kerja Buffer, Anda harus memahami tiga propertinya:
kapasitas
posisi
membatasi
Arti posisi dan batasan tergantung pada apakah Buffer berada dalam mode baca atau mode tulis. Apapun mode Buffernya, arti dari kapasitas selalu sama.
Berikut penjelasan mengenai kapasitas, posisi, dan limit pada mode baca dan tulis, penjelasan detailnya mengikuti ilustrasi.
kapasitas
Sebagai blok memori, Buffer memiliki nilai ukuran tetap, disebut juga "kapasitas". Anda hanya dapat menulis kapasitas byte, long, char dan tipe lainnya ke dalamnya. Setelah Buffer penuh, Buffer perlu dikosongkan (dengan membaca data atau menghapus data) sebelum penulisan data dapat dilanjutkan.
posisi
Saat Anda menulis data ke Buffer, posisi mewakili posisi saat ini. Nilai posisi awal adalah 0. Ketika data byte, panjang, dll ditulis ke Buffer, posisi akan berpindah ke unit Buffer berikutnya di mana data dapat dimasukkan. Posisi maksimal bisa kapasitas 1.
Saat data dibaca, data juga dibaca dari lokasi tertentu. Saat mengalihkan Buffer dari mode tulis ke mode baca, posisinya akan direset ke 0. Ketika data dibaca dari posisi Buffer, posisinya berpindah ke posisi berikutnya yang dapat dibaca.
membatasi
Dalam mode tulis, batas Buffer menunjukkan jumlah maksimum data yang dapat Anda tulis ke Buffer. Dalam mode tulis, batasnya sama dengan kapasitas Buffer.
Saat mengalihkan Buffer ke mode baca, batas menunjukkan jumlah maksimum data yang dapat Anda baca. Oleh karena itu, ketika mengalihkan Buffer ke mode baca, batas akan ditetapkan ke nilai posisi dalam mode tulis. Dengan kata lain, Anda dapat membaca semua data yang ditulis sebelumnya (batas diatur pada jumlah data yang ditulis, nilai ini diposisikan dalam mode tulis)
Jenis penyangga
Java NIO memiliki tipe Buffer berikut:
ByteBuffer
MappedByteBuffer
CharBuffer
Penyangga Ganda
FloatBuffer
IntBuffer
Penyangga Panjang
Buffer Pendek
Seperti yang Anda lihat, tipe Buffer ini mewakili tipe data yang berbeda. Dengan kata lain, byte dalam buffer dapat dimanipulasi melalui tipe char, short, int, long, float atau double.
MappedByteBuffer agak istimewa dan akan dibahas dalam bab tersendiri.
Alokasi penyangga
Untuk mendapatkan objek Buffer, Anda harus mengalokasikannya terlebih dahulu. Setiap kelas Buffer memiliki metode alokasi. Di bawah ini adalah contoh pengalokasian ByteBuffer dengan kapasitas 48 byte.
Copy kode kodenya sebagai berikut:
ByteBuffer buf = ByteBuffer.alokasikan(48);
Ini mengalokasikan CharBuffer yang dapat menyimpan 1024 karakter:
Copy kode kodenya sebagai berikut:
CharBuffer buf = CharBuffer.alokasikan(1024);
Tulis data ke Buffer
Ada dua cara untuk menulis data ke Buffer:
Menulis dari Saluran ke Buffer.
Menulis ke Buffer melalui metode put() Buffer.
Contoh penulisan dari Channel ke Buffer
Copy kode kodenya sebagai berikut:
int bytesRead = inChannel.read(buf); //membaca ke dalam buffer.
Contoh penulisan Buffer melalui metode put:
Copy kode kodenya sebagai berikut:
buf.put(127);
Ada banyak versi metode put, memungkinkan Anda menulis data ke Buffer dengan cara berbeda. Misalnya, menulis ke lokasi tertentu, atau menulis array byte ke Buffer. Untuk detail lebih lanjut tentang implementasi Buffer, lihat JavaDoc.
balik() metode
Metode flip mengalihkan Buffer dari mode tulis ke mode baca. Memanggil metode flip() akan mengembalikan posisi ke 0 dan menetapkan batas nilai posisi sebelumnya.
Dengan kata lain, posisi sekarang digunakan untuk menandai posisi membaca, dan batas mewakili berapa banyak byte, karakter, dll. yang ditulis sebelumnya - berapa banyak byte, karakter, dll. yang dapat dibaca sekarang.
Membaca data dari Buffer
Ada dua cara untuk membaca data dari Buffer:
Membaca data dari Buffer ke Channel.
Gunakan metode get() untuk membaca data dari Buffer.
Contoh pembacaan data dari Buffer ke Channel:
Copy kode kodenya sebagai berikut:
//membaca dari buffer ke saluran.
int bytesWritten = inChannel.write(buf);
Contoh penggunaan metode get() untuk membaca data dari Buffer
Copy kode kodenya sebagai berikut:
byte aByte = buf.get();
Ada banyak versi metode get yang memungkinkan Anda membaca data dari Buffer dengan cara berbeda. Misalnya membaca dari posisi tertentu, atau membaca data dari Buffer ke dalam array byte. Untuk detail lebih lanjut tentang implementasi Buffer, lihat JavaDoc.
mundur() metode
Buffer.rewind() menyetel posisi kembali ke 0, sehingga Anda dapat membaca ulang semua data di Buffer. Batasnya tetap tidak berubah dan masih menunjukkan berapa banyak elemen (byte, char, dll.) yang dapat dibaca dari Buffer.
metode clear() dan compact()
Setelah data dalam Buffer dibaca, Buffer harus siap untuk ditulis kembali. Hal ini dapat dilakukan melalui metode clear() atau compact().
Jika metode clear() dipanggil, posisi akan disetel kembali ke 0 dan batas akan disetel ke nilai kapasitas. Dengan kata lain, Buffer telah dihapus. Data dalam Buffer tidak dihapus, namun tanda ini memberitahu kita di mana harus mulai menulis data ke dalam Buffer.
Jika ada beberapa data yang belum dibaca di Buffer dan Anda memanggil metode clear(), data tersebut akan "terlupakan", yang berarti tidak akan ada lagi penanda yang memberi tahu Anda data mana yang sudah dibaca dan mana yang belum.
Jika masih ada data yang belum dibaca di Buffer dan data tersebut diperlukan nanti, namun Anda ingin menulis beberapa data terlebih dahulu, gunakan metode compact().
Metode compact() menyalin semua data yang belum dibaca ke awal Buffer. Kemudian atur posisinya tepat di belakang elemen terakhir yang belum dibaca. Atribut limit masih disetel ke kapasitas seperti metode clear(). Buffer sekarang siap untuk menulis data, namun data yang belum dibaca tidak akan ditimpa.
tandai() dan reset() metode
Dengan memanggil metode Buffer.mark(), Anda dapat menandai posisi tertentu di Buffer. Anda nanti dapat memulihkan ke posisi ini dengan memanggil metode Buffer.reset(). Misalnya:
Copy kode kodenya sebagai berikut:
buffer.tanda();
//panggil buffer.get() beberapa kali, misalnya saat parsing.
buffer.reset();//mengatur posisi kembali ke tanda.
metode sama dengan() dan bandingkanTo()
Anda dapat menggunakan metode sama dengan() dan membandingkanTo() untuk dua Buffer.
sama dengan()
Jika kondisi berikut terpenuhi, berarti kedua Buffer tersebut sama:
Memiliki tipe yang sama (byte, char, int, dll).
Jumlah sisa byte, karakter, dll. di Buffer adalah sama.
Semua sisa byte, karakter, dll. di Buffer adalah sama.
Seperti yang Anda lihat, sama dengan hanya membandingkan sebagian dari Buffer, tidak setiap elemen di dalamnya. Faktanya, ini hanya membandingkan elemen yang tersisa di Buffer.
bandingkanTo() metode
Metode bandingkanTo() membandingkan elemen yang tersisa (byte, char, dll.) dari dua Buffer Jika kondisi berikut terpenuhi, satu Buffer dianggap "kurang" dari Buffer lainnya:
Elemen tak sama pertama lebih kecil dari elemen bersesuaian di Buffer lainnya.
Semua elemen sama, tetapi Buffer pertama habis sebelum elemen lainnya (Buffer pertama memiliki elemen lebih sedikit dibandingkan elemen lainnya).
(Anotasi: Elemen sisanya adalah elemen dari posisi hingga batas)
Menyebar/Mengumpulkan
(Alamat asli bagian ini, penulis: Jakob Jenkov, penerjemah: Guo Lei)
Java NIO mulai mendukung scatter/gather. Scatter/gather digunakan untuk menggambarkan operasi membaca dari atau menulis ke Saluran (Catatan Penerjemah: Saluran sering diterjemahkan sebagai saluran dalam bahasa Cina).
Membaca sebar dari Saluran berarti menulis data yang dibaca ke dalam beberapa buffer selama operasi membaca. Oleh karena itu, Saluran "menyebarkan" data yang dibaca dari Saluran ke beberapa Buffer.
Mengumpulkan dan menulis ke Saluran berarti menulis data dari beberapa buffer ke Saluran yang sama selama operasi penulisan. Oleh karena itu, Saluran "mengumpulkan" data dalam beberapa Buffer dan mengirimkannya ke Saluran.
Scatter/gather sering digunakan dalam situasi di mana data yang dikirimkan perlu diproses secara terpisah. Misalnya, ketika mengirimkan pesan yang terdiri dari header pesan dan badan pesan, Anda dapat menyebarkan badan pesan dan header pesan ke dalam buffer yang berbeda, sehingga Anda dapat dengan mudah Memproses header pesan dan isi pesan.
Bacaan Hamburan
Bacaan Hamburan mengacu pada membaca data dari satu saluran ke beberapa buffer. Seperti yang dijelaskan pada gambar di bawah ini:
Contoh kodenya seperti berikut:
Copy kode kodenya sebagai berikut:
Header ByteBuffer = ByteBuffer.alokasikan(128);
ByteBuffer body = ByteBuffer.alokasikan(1024);
ByteBuffer[] bufferArray = { header, isi };
saluran.baca(bufferArray);
Perhatikan bahwa buffer pertama kali dimasukkan ke dalam array, dan kemudian array digunakan sebagai parameter input ke channel.read(). Metode read() menulis data yang dibaca dari saluran ke buffer sesuai urutan buffer dalam array. Ketika satu buffer terisi, saluran menulis ke buffer lain.
Bacaan Penyebaran harus mengisi buffer saat ini sebelum berpindah ke buffer berikutnya, yang juga berarti bahwa buffer tersebut tidak cocok untuk pesan dinamis (Catatan Penerjemah: Ukuran pesan tidak tetap). Dengan kata lain, jika ada header pesan dan isi pesan, header pesan harus terisi penuh (misalnya 128byte) agar Scattering Reads dapat berfungsi dengan baik.
Mengumpulkan Tulisan
Gathering Writes berarti data ditulis dari beberapa buffer ke saluran yang sama. Seperti yang dijelaskan pada gambar di bawah ini:
Contoh kodenya seperti berikut:
Copy kode kodenya sebagai berikut:
Header ByteBuffer = ByteBuffer.alokasikan(128);
ByteBuffer body = ByteBuffer.alokasikan(1024);
//menulis data ke dalam buffer
ByteBuffer[] bufferArray = { header, isi };
saluran.tulis(bufferArray);
Array buffer adalah parameter masukan dari metode write(). Metode write() akan menulis data ke saluran sesuai urutan buffer dalam array. Perhatikan bahwa hanya data antara posisi dan batas yang akan ditulis. Oleh karena itu, jika suatu buffer mempunyai kapasitas 128 byte tetapi hanya berisi 58 byte data, maka 58 byte data tersebut akan ditulis ke saluran tersebut. Oleh karena itu, berbeda dengan Scattering Reads, Gathering Writes dapat menangani pesan dinamis dengan lebih baik.
Transfer data antar saluran
(Alamat asli bagian ini, penulis: Jakob Jenkov, penerjemah: Guo Lei, korektor: Zhou Tai)
Di Java NIO, jika salah satu dari dua saluran adalah FileChannel, maka Anda dapat langsung mentransfer data dari satu saluran (Catatan Penerjemah: saluran sering diterjemahkan sebagai saluran dalam bahasa Cina) ke saluran lain.
transferDari()
Metode transferFrom() dari FileChannel dapat mentransfer data dari saluran sumber ke FileChannel (Catatan Penerjemah: Metode ini dijelaskan dalam dokumentasi JDK sebagai mentransfer byte dari saluran byte tertentu yang dapat dibaca ke file saluran ini. ). Berikut ini contoh sederhananya:
Copy kode kodenya sebagai berikut:
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel dariChannel = dariFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
posisi panjang = 0;
hitungan panjang = fromChannel.size();
toChannel.transferFrom(posisi, hitungan, fromChannel);
Parameter input position dari metode ini menunjukkan mulai dari posisi untuk menulis data ke file target, dan count menunjukkan jumlah maksimum byte yang ditransfer. Jika saluran sumber memiliki sisa ruang kurang dari jumlah byte, jumlah byte yang ditransfer kurang dari jumlah byte yang diminta.
Selain itu, perlu dicatat bahwa dalam implementasi SoketChannel, SocketChannel hanya akan mengirimkan data yang disiapkan saat ini (yang mungkin kurang dari hitungan byte). Oleh karena itu, SocketChannel tidak boleh mentransfer semua data yang diminta (jumlah byte) ke dalam FileChannel.
transferKe()
Metode transferTo() mentransfer data dari FileChannel ke saluran lain. Berikut ini contoh sederhananya:
Copy kode kodenya sebagai berikut:
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel dariChannel = dariFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
posisi panjang = 0;
hitungan panjang = fromChannel.size();
fromChannel.transferTo(posisi, hitungan, keChannel);
Apakah menurut Anda contoh ini sangat mirip dengan contoh sebelumnya? Kecuali objek FileChannel yang memanggil metode ini berbeda, semuanya sama.
Masalah yang disebutkan di atas tentang SocketChannel juga ada pada metode transferTo(). SocketChannel akan terus mengirimkan data hingga buffer target terisi.
Pemilih
(Tautan ke teks asli bagian ini, penulis: Jakob Jenkov, penerjemah: Langjiv, korektor: Ding Yi)
Selector adalah komponen di Java NIO yang dapat mendeteksi satu atau lebih saluran NIO dan mengetahui apakah saluran tersebut siap untuk kejadian seperti baca dan tulis. Dengan cara ini, satu thread dapat mengelola banyak saluran dan juga beberapa koneksi jaringan.
(1) Mengapa menggunakan Pemilih?
Keuntungan menggunakan hanya satu thread untuk menangani beberapa Saluran adalah lebih sedikit thread yang diperlukan untuk menangani saluran. Faktanya, dimungkinkan untuk hanya menggunakan satu thread untuk menangani semua saluran. Untuk sistem operasi, peralihan konteks antar thread sangat mahal, dan setiap thread menggunakan beberapa sumber daya sistem (seperti memori). Oleh karena itu, semakin sedikit benang yang digunakan, semakin baik.
Namun, perlu diingat bahwa sistem operasi dan CPU modern semakin baik dalam melakukan multitasking, sehingga overhead multithreading menjadi semakin kecil seiring berjalannya waktu. Faktanya, jika sebuah CPU memiliki banyak inti, tidak menggunakan multitasking mungkin akan membuang-buang daya CPU. Pokoknya pembahasan desain itu harusnya ada di artikel berbeda. Di sini, cukup mengetahui bahwa Anda dapat menangani banyak saluran menggunakan Selector.
Berikut ini adalah contoh diagram thread tunggal yang menggunakan Selector untuk memproses tiga saluran:
(2) Pembuatan Pemilih
Buat Selector dengan memanggil metode Selector.open(), sebagai berikut:
Copy kode kodenya sebagai berikut:
Pemilih pemilih = Selector.open();
(3) Daftarkan saluran dengan Selector
Untuk menggunakan Saluran dan Pemilih secara bersamaan, saluran harus didaftarkan pada pemilih. Hal ini dicapai melalui metode SelectableChannel.register(), sebagai berikut:
Copy kode kodenya sebagai berikut:
saluran.configureBlocking(false);
Kunci SelectionKey = channel.register(pemilih,
Kunci Pilihan.OP_READ);
Bila digunakan dengan Selector, Channel harus dalam mode non-blocking. Ini berarti Anda tidak dapat menggunakan FileChannel dengan Selector karena FileChannel tidak dapat dialihkan ke mode non-pemblokiran. Saluran soket baik-baik saja.
Perhatikan parameter kedua dari metode register(). Ini adalah "kumpulan minat", yang berarti acara apa yang Anda minati saat mendengarkan Saluran melalui Pemilih. Ada empat jenis acara berbeda yang dapat disimak:
Menghubungkan
Menerima
Membaca
Menulis
Saluran yang memicu suatu peristiwa berarti acara tersebut sudah siap. Oleh karena itu, saluran yang berhasil terhubung ke server lain disebut "koneksi siap". Saluran soket server dikatakan "siap menerima" ketika siap menerima koneksi masuk. Saluran yang mempunyai data untuk dibaca dikatakan "siap-baca". Saluran yang menunggu untuk menulis data dapat dikatakan "siap menulis".
Keempat peristiwa ini diwakili oleh empat konstanta SelectionKey:
Kunci Pilihan.OP_CONNECT
SelectionKey.OP_ACCEPT
Kunci Pilihan.OP_READ
Kunci Pilihan.OP_WRITE
Jika Anda tertarik pada lebih dari satu peristiwa, Anda dapat menggunakan operator bitwise OR untuk menghubungkan konstanta, sebagai berikut:
Copy kode kodenya sebagai berikut:
int interestSet = Kunci Pilihan.OP_READ |.Key Pilihan.OP_WRITE;
Koleksi minat akan disebutkan di bawah ini.
(4)Kunci Pilihan
Di bagian sebelumnya, saat mendaftarkan Saluran dengan Selector, metode register() mengembalikan objek SelectionKey. Objek ini berisi beberapa properti yang mungkin menarik bagi Anda:
pengumpulan bunga
koleksi siap
Saluran
Pemilih
Objek tambahan (opsional)
Di bawah ini saya jelaskan properti-properti tersebut.
pengumpulan bunga
Seperti yang dijelaskan pada bagian Mendaftarkan Channel dengan Selector, koleksi minat adalah kumpulan acara menarik yang Anda pilih. Anda dapat membaca dan menulis kumpulan minat melalui SelectionKey, seperti ini:
Copy kode kodenya sebagai berikut:
int interestSet = choiceKey.interess();
boolean isInterestedInAccept= (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
Terlihat bahwa dengan menggunakan "bit AND" untuk mengoperasikan kumpulan minat dan konstanta SelectionKey yang diberikan, Anda dapat menentukan apakah suatu peristiwa tertentu ada dalam kumpulan minat.
koleksi siap
Ready set adalah himpunan operasi yang salurannya sudah siap. Setelah pemilihan (Seleksi), Anda akan mengakses set yang sudah jadi terlebih dahulu. Seleksi akan dijelaskan pada bagian selanjutnya. Koleksi siap dapat diakses seperti ini:
int readySet = choiceKey.readyOps();
Anda dapat menggunakan metode yang sama seperti mendeteksi kumpulan minat untuk mendeteksi peristiwa atau operasi apa yang siap di saluran. Namun, empat metode berikut juga tersedia, semuanya mengembalikan tipe Boolean:
Copy kode kodenya sebagai berikut:
seleksiKey.isAcceptable();
seleksiKey.isConnectable();
seleksiKey.isReadable();
seleksiKey.isWritable();
Saluran+Pemilih
Mengakses Saluran dan Pemilih dari SelectionKey itu sederhana. sebagai berikut:
Copy kode kodenya sebagai berikut:
Saluransaluran=selectionKey.saluran();
Pemilih pemilih = choiceKey.selector();
objek tambahan
Sebuah objek atau lebih banyak informasi dapat dilampirkan ke SelectionKey untuk dengan mudah mengidentifikasi saluran tertentu. Misalnya, Anda bisa melampirkan Buffer untuk digunakan dengan saluran, atau objek yang berisi data gabungan. Cara menggunakannya:
Copy kode kodenya sebagai berikut:
seleksiKey.lampirkan(Objek);
Objek terlampirObj = choiceKey.attachment();
Anda juga dapat melampirkan objek saat mendaftarkan Saluran dengan Selector menggunakan metode register(). menyukai:
Copy kode kodenya sebagai berikut:
Kunci SelectionKey = channel.register(selector, SelectionKey.OP_READ, theObject);
(5) Pilih saluran melalui Selector
Setelah satu atau lebih saluran didaftarkan dengan Selector, beberapa metode select() yang kelebihan beban dapat dipanggil. Metode ini mengembalikan saluran yang siap untuk acara yang Anda minati (seperti menghubungkan, menerima, membaca, atau menulis). Dengan kata lain, jika Anda tertarik dengan saluran "read-ready", metode select() akan mengembalikan saluran yang acara bacanya sudah siap.
Berikut adalah metode pilih():
ke dalam pilih()
int pilih (waktu tunggu lama)
ke dalam pilihSekarang()
select() memblokir hingga setidaknya satu saluran siap untuk acara yang Anda daftarkan.
select(long timeout) sama dengan select(), hanya saja ia akan memblokir hingga batas waktu milidetik (parameter).
selectNow() tidak memblokir dan segera kembali tidak peduli saluran apa yang siap (Catatan Penerjemah: Metode ini melakukan operasi pemilihan non-pemblokiran. Jika tidak ada saluran yang dapat dipilih sejak operasi pemilihan sebelumnya, metode ini langsung mengembalikan nol.).
Nilai int yang dikembalikan oleh metode select() menunjukkan berapa banyak saluran yang siap. Artinya, berapa banyak saluran yang telah siap sejak pemanggilan terakhir ke metode select(). Jika metode select() dipanggil, 1 dikembalikan karena satu saluran sudah siap. Jika metode select() dipanggil lagi, jika saluran lain sudah siap, maka akan mengembalikan 1 lagi. Jika tidak ada operasi yang dilakukan pada saluran siap pertama, sekarang ada dua saluran siap, namun di antara setiap pemanggilan metode select(), hanya satu saluran yang siap.
Kunci yang dipilih()
Setelah metode select() dipanggil dan nilai yang dikembalikan menunjukkan bahwa satu atau lebih saluran telah siap, saluran siap di "kumpulan kunci yang dipilih" kemudian dapat diakses dengan memanggil metode selectKeys() milik pemilih. Seperti yang ditunjukkan di bawah ini:
Copy kode kodenya sebagai berikut:
Setel SelectKeys = selector.selectedKeys();
Saat mendaftarkan Saluran seperti Selector, metode Channel.register() mengembalikan objek SelectionKey. Objek ini mewakili saluran yang terdaftar pada Selector. Objek-objek ini dapat diakses melalui metodeselectKeySet() SelectionKey.
Saluran yang sudah siap dapat diakses dengan melintasi rangkaian tombol yang dipilih ini. sebagai berikut:
Copy kode kodenya sebagai berikut:
Setel SelectKeys = selector.selectedKeys();
Iterator keyIterator = dipilihKeys.iterator();
while(keyIterator.hasNext()) {
Kunci SelectionKey = keyIterator.next();
if(key.isAcceptable()) {
// koneksi diterima oleh ServerSocketChannel.
} lain jika (kunci.isConnectable()) {
// koneksi dibuat dengan server jarak jauh.
} lain jika (kunci.isReadable()) {
// saluran siap dibaca
} lain jika (kunci.isWritable()) {
// saluran siap untuk ditulis
}
keyIterator.<tuihighlight><a href="javascript:;" style="display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text-decoration:underline;">hapus</a ></tuihighlight>();
}
Perulangan ini mengulangi setiap kunci dalam rangkaian kunci yang dipilih dan mendeteksi peristiwa siap pakai untuk saluran yang sesuai dengan setiap kunci.
Catat panggilan keyIterator.remove() di akhir setiap iterasi. Selector tidak menghapus instance SelectionKey dari kumpulan kunci yang dipilih itu sendiri. Harus dihapus sendiri ketika saluran diproses. Saat berikutnya saluran sudah siap, Selector akan memasukkannya ke dalam kumpulan kunci yang dipilih lagi.
Saluran yang dikembalikan oleh metode SelectionKey.channel() perlu diubah menjadi tipe yang ingin Anda proses, seperti ServerSocketChannel atau SocketChannel, dll.
(6)bangun()
Sebuah thread diblokir setelah memanggil metode select(). Meskipun tidak ada saluran yang siap, ada cara untuk mengembalikannya dari metode select(). Biarkan thread lain memanggil metode Selector.wakeup() pada objek tempat thread pertama memanggil metode select(). Thread yang diblokir pada metode select() akan segera kembali.
Jika thread lain memanggil metode wakeup(), namun saat ini tidak ada thread yang diblokir pada metode select(), thread berikutnya yang memanggil metode select() akan segera "bangun".
(7) tutup()
Memanggil metode close() setelah menggunakan Selector akan menutup Selector dan membatalkan semua instance SelectionKey yang terdaftar pada Selector. Saluran itu sendiri tidak menutup.
(8) Contoh lengkap
Berikut contoh lengkapnya, buka Selector, daftarkan saluran ke Selector (proses inisialisasi saluran dihilangkan), lalu pantau terus apakah keempat event Selector (terima, sambungkan, baca, tulis) sudah siap.
Copy kode kodenya sebagai berikut:
Pemilih pemilih = Selector.open();
saluran.configureBlocking(false);
Kunci SelectionKey = channel.register(selector, SelectionKey.OP_READ);
sementara(benar) {
int readyChannels = selector.select();
if(readyChannels == 0) lanjutkan;
Setel SelectKeys = selector.selectedKeys();
Iterator keyIterator = dipilihKeys.iterator();
while(keyIterator.hasNext()) {
Kunci SelectionKey = keyIterator.next();
if(key.isAcceptable()) {
// koneksi diterima oleh ServerSocketChannel.
} lain jika (kunci.isConnectable()) {
// koneksi dibuat dengan server jarak jauh.
} lain jika (kunci.isReadable()) {
// saluran siap dibaca
} lain jika (kunci.isWritable()) {
// saluran siap untuk ditulis
}
keyIterator.<tuihighlight><a href="javascript:;" style="display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text-decoration:underline;">hapus</a ></tuihighlight>();
}
}
saluran berkas
(Tautan ke teks asli bagian ini, penulis: Jakob Jenkov, penerjemah: Zhou Tai, korektor: Ding Yi)
FileChannel di Java NIO adalah saluran yang terhubung ke suatu file. File dapat dibaca dan ditulis melalui saluran file.
FileChannel tidak dapat disetel ke mode non-pemblokiran, selalu berjalan dalam mode pemblokiran.
OpenFileChannel
Sebelum menggunakan FileChannel, harus dibuka. Namun, kita tidak dapat membuka FileChannel secara langsung. Kita perlu mendapatkan instance FileChannel dengan menggunakan InputStream, OutputStream, atau RandomAccessFile. Berikut adalah contoh membuka FileChannel melalui RandomAccessFile:
Copy kode kodenya sebagai berikut:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel diChannel = aFile.getChannel();
Membaca data dari FileChannel
Panggil salah satu dari beberapa metode read() untuk membaca data dari FileChannel. menyukai:
Copy kode kodenya sebagai berikut:
ByteBuffer buf = ByteBuffer.alokasikan(48);
int bytesRead = inChannel.read(buf);
Pertama, alokasikan Buffer. Data yang dibaca dari FileChannel akan dibaca ke dalam Buffer.
Kemudian, panggil metode FileChannel.read(). Metode ini membaca data dari FileChannel ke Buffer. Nilai int yang dikembalikan oleh metode read() menunjukkan berapa banyak byte yang dibaca ke dalam Buffer. Jika mengembalikan -1, berarti akhir file telah tercapai.
Tulis data ke FileChannel
Gunakan metode FileChannel.write() untuk menulis data ke FileChannel. Parameter metode ini adalah Buffer. menyukai:
Copy kode kodenya sebagai berikut:
String newData = "String baru untuk ditulis ke file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.alokasikan(48);
buf.hapus();
buf.put(data baru.getBytes());
buf.flip();
while(buf.hasRemaining()) {
saluran.tulis(buf);
}
Perhatikan bahwa FileChannel.write() dipanggil dalam perulangan while. Karena tidak ada jaminan berapa byte yang dapat ditulis oleh metode write() ke FileChannel dalam satu waktu, metode write() perlu dipanggil berulang kali hingga tidak ada byte di Buffer yang belum ditulis ke saluran.
Tutup FileChannel
FileChannel harus ditutup ketika Anda selesai menggunakannya. menyukai:
Copy kode kodenya sebagai berikut:
saluran.close();
Metode posisi FileChannel
Terkadang mungkin perlu membaca/menulis data di lokasi tertentu di FileChannel. Anda bisa mendapatkan posisi FileChannel saat ini dengan memanggil metode position().
Anda juga dapat mengatur posisi FileChannel saat ini dengan memanggil metode position(long pos).
Berikut dua contohnya:
Copy kode kodenya sebagai berikut:
long pos = saluran.posisi();
saluran.posisi(pos +123);
Jika Anda mengatur posisi setelah akhir file dan kemudian mencoba membaca data dari saluran file, metode baca akan mengembalikan -1 - tanda akhir file.
Jika Anda mengatur posisi setelah akhir file dan kemudian menulis data ke saluran, file akan diperluas ke posisi saat ini dan data akan ditulis. Hal ini dapat menyebabkan "lubang file", kesenjangan antara data yang ditulis dalam file fisik pada disk.
Metode ukuran FileChannel
Metode size() dari instance FileChannel akan mengembalikan ukuran file yang terkait dengan instance tersebut. menyukai:
Copy kode kodenya sebagai berikut:
ukuran file panjang = saluran.ukuran();
Metode pemotongan FileChannel
Anda dapat menggunakan metode FileChannel.truncate() untuk mencegat file. Saat mencegat file, bagian setelah panjang file yang ditentukan akan dihapus. menyukai:
Copy kode kodenya sebagai berikut:
saluran.truncate(1024);
Contoh ini memotong 1024 byte pertama file.
Metode paksa FileChannel
Metode FileChannel.force() memaksa data dalam saluran yang belum ditulis ke disk ke disk. Untuk alasan kinerja, sistem operasi menyimpan data dalam memori, sehingga tidak ada jaminan bahwa data yang ditulis ke FileChannel akan segera ditulis ke disk. Untuk memastikan hal ini, metode force() perlu dipanggil.
Metode force() memiliki parameter boolean yang menunjukkan apakah akan menulis metadata file (informasi izin, dll.) ke disk secara bersamaan.
Contoh berikut memaksa data file dan metadata ke disk:
Copy kode kodenya sebagai berikut:
saluran.force(benar);
Saluran soket
(Tautan ke teks asli bagian ini, penulis: Jakob Jenkov, penerjemah: Zheng Yuting, korektor: Ding Yi)
SocketChannel di Java NIO adalah saluran yang terhubung ke soket jaringan TCP. SocketChannel dapat dibuat dengan 2 cara berikut:
Buka SocketChannel dan sambungkan ke server di Internet.
Ketika koneksi baru tiba di ServerSocketChannel, SocketChannel dibuat.
Buka SocketChannel
Berikut cara membuka SocketChannel:
Copy kode kodenya sebagai berikut:
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(InetSocketAddress baru("http://jenkov.com", 80));
Tutup SocketChannel
Ketika Anda selesai dengan SocketChannel, panggil SocketChannel.close() untuk menutup SocketChannel:
Copy kode kodenya sebagai berikut:
socketChannel.close();
Membaca data dari SocketChannel
Untuk membaca data dari SocketChannel, panggil salah satu metode read(). Berikut ini contohnya:
Copy kode kodenya sebagai berikut:
ByteBuffer buf = ByteBuffer.alokasikan(48);
int byteBaca = soketSaluran.baca(buf);
Pertama, alokasikan Buffer. Data yang dibaca dari SocketChannel akan ditempatkan di Buffer ini.
Lalu, panggil SocketChannel.read(). Metode ini membaca data dari SocketChannel ke Buffer. Nilai int yang dikembalikan oleh metode read() menunjukkan berapa banyak byte yang dibaca ke dalam Buffer. Jika -1 dikembalikan, berarti akhir aliran telah dibaca (koneksi ditutup).
Menulis ke SocketChannel
Menulis data ke SocketChannel menggunakan metode SocketChannel.write(), yang menggunakan Buffer sebagai parameter. Contohnya adalah sebagai berikut:
Copy kode kodenya sebagai berikut:
String newData = "String baru untuk ditulis ke file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.alokasikan(48);
buf.hapus();
buf.put(data baru.getBytes());
buf.flip();
while(buf.hasRemaining()) {
saluran.tulis(buf);
}
Perhatikan bahwa metode SocketChannel.write() dipanggil dalam perulangan while. Metode Write() tidak dapat menjamin berapa banyak byte yang dapat ditulis ke SocketChannel. Jadi, kami memanggil write() berulang kali hingga Buffer tidak memiliki byte tersisa untuk ditulis.
mode non-pemblokiran
Anda dapat mengatur SocketChannel ke mode non-pemblokiran. Setelah pengaturan, Anda dapat memanggil connect(), read() dan write() dalam mode asynchronous.
menghubungkan()
Jika SocketChannel berada dalam mode non-pemblokiran dan connect() dipanggil saat ini, metode tersebut dapat kembali sebelum koneksi dibuat. Untuk menentukan apakah koneksi telah dibuat, Anda dapat memanggil metode finishConnect(). Seperti ini:
Copy kode kodenya sebagai berikut:
socketChannel.configureBlocking (salah);
socketChannel.connect(InetSocketAddress baru("http://jenkov.com", 80));
while(!socketChannel.finishConnect() ){
//tunggu, atau lakukan hal lain...
}
menulis()
Dalam mode non-pemblokiran, metode write() dapat kembali sebelum menulis apa pun. Jadi write() perlu dipanggil dalam loop. Ada contoh sebelumnya, jadi saya tidak akan menjelaskan detailnya di sini.
membaca()
Dalam mode non-pemblokiran, metode read() dapat kembali sebelum data apa pun dibaca. Jadi, Anda perlu memperhatikan nilai pengembalian intnya, yang akan memberi tahu Anda berapa banyak byte yang dibaca.
Mode dan penyeleksi non-pemblokiran
Mode non-pemblokiran bekerja lebih baik dengan penyeleksi. Dengan mendaftarkan satu atau lebih SocketChannels dengan Selector, Anda dapat menanyakan kepada pemilih saluran mana yang siap untuk membaca, menulis, dll. Kombinasi Selector dan SocketChannel akan dibahas secara rinci nanti.
Saluran ServerSocket
(Tautan ke teks asli bagian ini, penulis: Jakob Jenkov, penerjemah: Zheng Yuting, korektor: Ding Yi)
ServerSocketChannel di Java NIO adalah saluran yang dapat mendengarkan koneksi TCP baru yang masuk, seperti ServerSocket di IO standar. Kelas ServerSocketChannel ada dalam paket java.nio.channels.
Berikut ini contohnya:
Copy kode kodenya sebagai berikut:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(InetSocketAddress(9999));
sementara(benar){
SocketChannel socketChannel =
serverSocketChannel.accept();
//melakukan sesuatu dengan socketChannel...
}
Buka ServerSocketChannel
Buka ServerSocketChannel dengan memanggil metode ServerSocketChannel.open() Misalnya:
Copy kode kodenya sebagai berikut:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
Tutup ServerSocketChannel
Tutup ServerSocketChannel dengan memanggil metode ServerSocketChannel.close() Misalnya:
Copy kode kodenya sebagai berikut:
serverSocketChannel.close();
Dengarkan koneksi masuk baru
Dengarkan koneksi masuk baru melalui metode ServerSocketChannel.accept(). Ketika metode terima() kembali, ia mengembalikan SocketChannel yang berisi koneksi masuk baru. Oleh karena itu, metode terima() akan memblokir hingga koneksi baru tiba.
Biasanya, daripada hanya mendengarkan satu koneksi, metode terima() dipanggil dalam perulangan while. Seperti pada contoh berikut:
Copy kode kodenya sebagai berikut:
sementara(benar){
SocketChannel socketChannel =
serverSocketChannel.accept();
//melakukan sesuatu dengan socketChannel...
}
Tentu saja, Anda juga dapat menggunakan kriteria keluar lainnya selain true di perulangan while.
mode non-pemblokiran
ServerSocketChannel dapat diatur ke mode non-pemblokiran. Dalam mode non-pemblokiran, metode terima() akan segera kembali. Jika tidak ada koneksi masuk baru, nilai yang dikembalikan akan menjadi nol. Oleh karena itu, Anda perlu memeriksa apakah SocketChannel yang dikembalikan adalah null. menyukai:
Copy kode kodenya sebagai berikut:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(InetSocketAddress(9999));
serverSocketChannel.configureBlocking(salah);
sementara(benar){
SocketChannel socketChannel =
serverSocketChannel.accept();
jika(socketChannel != null){
//melakukan sesuatu dengan socketChannel...
}
}
saluran datagram
(Tautan ke teks asli bagian ini, penulis: Jakob Jenkov, penerjemah: Zheng Yuting, korektor: Ding Yi)
DatagramChannel di Java NIO adalah saluran yang dapat mengirim dan menerima paket UDP. Karena UDP adalah protokol jaringan tanpa koneksi, maka UDP tidak dapat dibaca dan ditulis seperti saluran lainnya. Ia mengirim dan menerima paket data.
OpenDatagramChannel
Berikut cara DatagramChannel dibuka:
Copy kode kodenya sebagai berikut:
Saluran DatagramChannel = DatagramChannel.open();
saluran.socket().bind(InetSocketAddress(9999));
DatagramChannel yang dibuka pada contoh ini dapat menerima paket pada port UDP 9999.
menerima data
Menerima data dari DatagramChannel melalui metode terima(), seperti:
Copy kode kodenya sebagai berikut:
ByteBuffer buf = ByteBuffer.alokasikan(48);
buf.hapus();
saluran.terima(buf);
Metode terima() akan menyalin isi paket data yang diterima ke Buffer yang ditentukan. Jika Buffer tidak dapat menampung data yang diterima, kelebihan data akan dibuang.
Kirim data
Kirim data dari DatagramChannel melalui metode send(), seperti:
Copy kode kodenya sebagai berikut:
String newData = "String baru untuk ditulis ke file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.alokasikan(48);
buf.hapus();
buf.put(data baru.getBytes());
buf.flip();
int bytesSent = saluran.kirim(buf, InetSocketAddress baru("jenkov.com", 80));
Contoh ini mengirimkan serangkaian karakter ke port UDP 80 pada server "jenkov.com". Karena server tidak memantau port ini, tidak akan terjadi apa-apa. Ia juga tidak akan memberi tahu Anda apakah paket keluar telah diterima, karena UDP tidak memiliki jaminan apa pun dalam hal pengiriman data.
Hubungkan ke alamat tertentu
DatagramChannel dapat "dihubungkan" ke alamat tertentu di jaringan. Karena UDP tidak memiliki koneksi, menghubungkan ke alamat tertentu tidak membuat koneksi nyata seperti saluran TCP. Sebaliknya, DatagramChannel dikunci sehingga hanya dapat mengirim dan menerima data dari alamat tertentu.
Berikut ini contohnya:
Copy kode kodenya sebagai berikut:
saluran.connect(InetSocketAddress baru("jenkov.com", 80));
Setelah terhubung, Anda juga dapat menggunakan metode read() dan write() seperti yang Anda lakukan pada saluran tradisional. Tidak ada jaminan mengenai transfer data. Berikut beberapa contohnya:
Copy kode kodenya sebagai berikut:
int byteBaca = saluran.baca(buf);
int bytesWritten = saluran.tulis(tetapi);
Pipa
(Tautan ke teks asli bagian ini, penulis: Jakob Jenkov, penerjemah: Huang Zhong, korektor: Ding Yi)
Pipa Java NIO adalah koneksi data satu arah antara 2 thread. Pipa mempunyai saluran sumber dan saluran pembuangan. Data akan ditulis ke saluran sink dan dibaca dari saluran sumber.
Berikut adalah ilustrasi prinsip Pipa:
Buat saluran pipa
Buka pipa melalui metode Pipe.open(). Misalnya:
Copy kode kodenya sebagai berikut:
Pipa pipa = Pipa.open();
Tulis data ke pipa
Untuk menulis data ke pipa, Anda perlu mengakses saluran sink. Seperti ini:
Copy kode kodenya sebagai berikut:
Pipa.SinkChannel sinkChannel = pipa.sink();
Tulis data ke SinkChannel dengan memanggil metode write() SinkChannel, seperti ini:
Copy kode kodenya sebagai berikut:
String newData = "String baru untuk ditulis ke file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.alokasikan(48);
buf.hapus();
buf.put(data baru.getBytes());
buf.flip();
while(buf.hasRemaining()) {
<b>sinkChannel.write(buf);</b>
}
[kode]
Membaca data dari pipa
Untuk membaca data dari pipa, Anda perlu mengakses saluran sumber, seperti ini:
[kode]
Pipa.SourceChannel sourceChannel = pipa.source();
Panggil metode read() saluran sumber untuk membaca data, seperti ini:
Copy kode kodenya sebagai berikut:
ByteBuffer buf = ByteBuffer.alokasikan(48);
int bytesRead = inChannel.read(buf);
Nilai int yang dikembalikan oleh metode read() akan memberi tahu kita berapa banyak byte yang dibaca ke dalam buffer.