Java NIO มีวิธีการทำงานของ IO ที่แตกต่างจาก IO มาตรฐาน:
แชนเนลและบัฟเฟอร์: IO มาตรฐานทำงานตามสตรีมไบต์และสตรีมอักขระ ในขณะที่ NIO ทำงานตามแชนเนล (แชนเนล) และบัฟเฟอร์ (บัฟเฟอร์) ข้อมูลจะถูกอ่านจากแชนเนลไปยังพื้นที่บัฟเฟอร์เสมอหรือเขียนจากบัฟเฟอร์ไปยัง ช่อง.
IO แบบอะซิงโครนัส: Java NIO อนุญาตให้คุณใช้ IO แบบอะซิงโครนัสได้ ตัวอย่างเช่น เมื่อเธรดอ่านข้อมูลจากช่องสัญญาณลงในบัฟเฟอร์ เธรดยังสามารถทำสิ่งอื่นได้ เมื่อข้อมูลถูกเขียนลงในบัฟเฟอร์ เธรดสามารถประมวลผลต่อไปได้ การเขียนไปยังช่องสัญญาณจากบัฟเฟอร์จะคล้ายกัน
ตัวเลือก: Java NIO แนะนำแนวคิดของตัวเลือก ซึ่งใช้ในการฟังเหตุการณ์บนหลายช่องทาง (เช่น การเปิดการเชื่อมต่อ การมาถึงของข้อมูล) ดังนั้นเธรดเดียวจึงสามารถฟังช่องข้อมูลได้หลายช่อง
เรามาแนะนำความรู้ที่เกี่ยวข้องของ Java NIO โดยละเอียดกัน
ภาพรวม Java NIO
Java NIO ประกอบด้วยส่วนหลักดังต่อไปนี้:
ช่อง
บัฟเฟอร์
ตัวเลือก
แม้ว่าจะมีคลาสและส่วนประกอบอื่นๆ มากมายใน Java NIO แต่ในความคิดของฉัน Channel, Buffer และ Selector ถือเป็น core API ส่วนประกอบอื่นๆ เช่น Pipe และ FileLock เป็นเพียงคลาสยูทิลิตี้ที่ใช้กับส่วนประกอบหลักทั้งสาม ดังนั้นในภาพรวมนี้ผมจะเน้นไปที่องค์ประกอบทั้งสามนี้ ส่วนประกอบอื่นๆ จะกล่าวถึงในบทที่แยกจากกัน
ช่องและบัฟเฟอร์
โดยพื้นฐานแล้ว IO ทั้งหมดใน NIO เริ่มต้นจาก Channel ช่องก็เหมือนสตรีมนิดหน่อย ข้อมูลสามารถอ่านจาก Channel ไปยัง Buffer หรือเขียนจาก Buffer ไปยัง Channel นี่คือภาพประกอบ:
มีช่องและบัฟเฟอร์หลายประเภท ต่อไปนี้เป็นการใช้งานแชนเนลหลักบางส่วนใน JAVA NIO:
ไฟล์แชนแนล
ดาต้าแกรมแชนเนล
ซ็อกเก็ตช่อง
ServerSocketChannel
อย่างที่คุณเห็น ช่องเหล่านี้ครอบคลุมเครือข่าย UDP และ TCP IO รวมถึงไฟล์ IO
นอกจากคลาสเหล่านี้แล้ว ยังมีอินเทอร์เฟซที่น่าสนใจอยู่บ้าง แต่เพื่อความเรียบง่าย ฉันพยายามที่จะไม่พูดถึงพวกมันในภาพรวม ฉันจะอธิบายสิ่งเหล่านั้นในบทอื่นๆ ของบทช่วยสอนนี้ที่เกี่ยวข้องกัน
ต่อไปนี้คือการใช้งานบัฟเฟอร์ที่สำคัญใน Java NIO:
ByteBuffer
ชาร์บัฟเฟอร์
ดับเบิลบัฟเฟอร์
FloatBuffer
IntBuffer
ลองบัฟเฟอร์
ชอร์ตบัฟเฟอร์
บัฟเฟอร์เหล่านี้ครอบคลุมประเภทข้อมูลพื้นฐานที่คุณสามารถส่งผ่าน IO: ไบต์, สั้น, int, ยาว, ลอย, สองเท่า และถ่าน
Java NIO ยังมี Mappyteuffer ซึ่งใช้เพื่อแสดงไฟล์ที่แมปหน่วยความจำ ฉันจะไม่อธิบายในภาพรวม
ตัวเลือก
ตัวเลือกอนุญาตให้เธรดเดียวจัดการหลายช่องสัญญาณ หากแอปพลิเคชันของคุณเปิดการเชื่อมต่อหลายรายการ (ช่องสัญญาณ) แต่การรับส่งข้อมูลของแต่ละการเชื่อมต่อต่ำมาก การใช้ตัวเลือกอาจสะดวกกว่า เช่น ในเซิร์ฟเวอร์แชท
นี่คือภาพประกอบของการใช้ Selector เพื่อประมวลผล 3 Channels ในเธรดเดียว:
หากต้องการใช้ Selector คุณต้องลงทะเบียน Channel ด้วย Selector แล้วเรียกใช้เมธอด select() วิธีนี้จะบล็อกจนกว่าช่องที่ลงทะเบียนจะมีกิจกรรมพร้อม เมื่อวิธีนี้กลับมา เธรดจะสามารถจัดการกับเหตุการณ์เหล่านี้ได้ ตัวอย่างของเหตุการณ์ เช่น การเชื่อมต่อใหม่ที่กำลังเข้ามา การรับข้อมูล เป็นต้น
Java NIO กับ IO
(ที่อยู่เดิมของส่วนนี้ ผู้แต่ง: Jakob Jenkov ผู้แปล: Guo Lei ผู้พิสูจน์อักษร: Fang Tengfei)
หลังจากเรียนรู้เกี่ยวกับ Java NIO และ IO API แล้ว คำถามก็เข้ามาในใจทันที:
อ้าง
ฉันควรใช้ IO เมื่อใด และควรใช้ NIO เมื่อใด ในบทความนี้ ฉันจะพยายามอธิบายความแตกต่างระหว่าง Java NIO และ IO สถานการณ์การใช้งาน และผลกระทบที่มีต่อการออกแบบโค้ดของคุณอย่างชัดเจน
ความแตกต่างหลักระหว่าง Java NIO และ IO
ตารางต่อไปนี้สรุปความแตกต่างที่สำคัญระหว่าง Java NIO และ IO ฉันจะอธิบายความแตกต่างในแต่ละส่วนของตารางโดยละเอียด
ไอโอ นีโอ
บัฟเฟอร์ที่เน้นสตรีม
การบล็อก IO ไม่บล็อก IO
ตัวเลือก
เน้นสตรีมและเน้นบัฟเฟอร์
ข้อแตกต่างที่ใหญ่ที่สุดประการแรกระหว่าง Java NIO และ IO คือ IO เน้นการสตรีม และ NIO เน้นบัฟเฟอร์ Java IO เป็นแบบสตรีมซึ่งหมายความว่าจะมีการอ่านอย่างน้อยหนึ่งไบต์จากสตรีมในแต่ละครั้ง และจะไม่ถูกแคชจนกว่าไบต์ทั้งหมดจะถูกอ่าน นอกจากนี้ยังไม่สามารถย้ายข้อมูลในสตรีมไปข้างหน้าหรือข้างหลังได้ หากคุณต้องการย้ายข้อมูลที่อ่านจากสตรีมไปมา คุณต้องแคชข้อมูลนั้นไว้ในบัฟเฟอร์ก่อน วิธีการเน้นบัฟเฟอร์ของ Java NIO นั้นแตกต่างออกไปเล็กน้อย ข้อมูลจะถูกอ่านลงในบัฟเฟอร์ที่ประมวลผลในภายหลัง โดยเลื่อนไปมาในบัฟเฟอร์ตามต้องการ สิ่งนี้จะเพิ่มความยืดหยุ่นในการประมวลผล อย่างไรก็ตาม คุณต้องตรวจสอบด้วยว่าบัฟเฟอร์มีข้อมูลทั้งหมดที่คุณต้องประมวลผลด้วย นอกจากนี้ ตรวจสอบให้แน่ใจว่าเมื่อมีการอ่านข้อมูลลงในบัฟเฟอร์มากขึ้น ข้อมูลที่ยังไม่ได้ประมวลผลในบัฟเฟอร์จะไม่ถูกเขียนทับ
การบล็อกและไม่บล็อก IO
สตรีมต่างๆ ของ Java IO กำลังบล็อกอยู่ ซึ่งหมายความว่าเมื่อเธรดเรียก read() หรือ write() เธรดจะถูกบล็อกจนกว่าข้อมูลบางส่วนจะถูกอ่าน หรือข้อมูลถูกเขียนอย่างสมบูรณ์ เธรดไม่สามารถดำเนินการอื่นใดได้ในช่วงเวลานี้ โหมดไม่บล็อกของ Java NIO อนุญาตให้เธรดส่งคำขอเพื่ออ่านข้อมูลจากบางช่องทาง แต่จะสามารถรับได้เฉพาะข้อมูลที่มีอยู่ในปัจจุบันเท่านั้น หากไม่มีข้อมูลใด ๆ จะได้รับ แทนที่จะบล็อกเธรด เธรดสามารถทำสิ่งอื่นต่อไปได้จนกว่าข้อมูลจะสามารถอ่านได้ เช่นเดียวกับการเขียนแบบไม่บล็อก เธรดร้องขอให้เขียนข้อมูลบางอย่างลงในแชนเนล แต่ไม่จำเป็นต้องรอให้เขียนเสร็จสมบูรณ์ เธรดสามารถทำสิ่งอื่นได้ในระหว่างนี้ โดยทั่วไปเธรดจะใช้เวลาว่างใน IO ที่ไม่มีการบล็อกเพื่อดำเนินการ IO บนช่องสัญญาณอื่น ดังนั้นเธรดเดียวจึงสามารถจัดการช่องอินพุตและเอาต์พุตหลายช่องได้
ตัวเลือก
ตัวเลือกของ Java NIO อนุญาตให้เธรดเดียวตรวจสอบช่องอินพุตหลายช่องได้โดยใช้ตัวเลือก จากนั้นใช้เธรดแยกต่างหากเพื่อ "เลือก" ช่อง: ช่องเหล่านี้มีอินพุตที่สามารถประมวลผลได้แล้ว พร้อมสำหรับการเขียน กลไกการเลือกนี้ทำให้เธรดเดียวจัดการหลายช่องได้ง่าย
NIO และ IO ส่งผลต่อการออกแบบแอปพลิเคชันอย่างไร
ไม่ว่าคุณจะเลือกกล่องเครื่องมือ IO หรือ NIO มีหลายแง่มุมที่อาจส่งผลต่อการออกแบบแอปพลิเคชันของคุณ:
การเรียก API ไปยังคลาส NIO หรือ IO
การประมวลผลข้อมูล
จำนวนเธรดที่ใช้ในการประมวลผลข้อมูล
การเรียก API
แน่นอนว่าการเรียก API เมื่อใช้ NIO ดูแตกต่างจากเมื่อใช้ IO แต่ก็ไม่ใช่เรื่องที่ไม่คาดคิด เนื่องจากแทนที่จะอ่านจากไบต์ InputStream ทีละไบต์ ข้อมูลจะต้องถูกอ่านลงในบัฟเฟอร์ก่อนแล้วจึงประมวลผล
การประมวลผลข้อมูล
การใช้การออกแบบ NIO ล้วนๆ เมื่อเปรียบเทียบกับการออกแบบ IO การประมวลผลข้อมูลก็ได้รับผลกระทบเช่นกัน
ในการออกแบบ IO เราอ่านข้อมูลแบบไบต์ต่อไบต์จาก InputStream หรือ Reader สมมติว่าคุณกำลังประมวลผลสตรีมข้อมูลข้อความแบบบรรทัด ตัวอย่างเช่น:
คัดลอกรหัสรหัสดังต่อไปนี้:
ชื่อ: แอนนา
อายุ: 25
อีเมล์: [email protected]
โทรศัพท์: 1234567890
กระแสของบรรทัดข้อความสามารถจัดการได้ดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
InputStream input = … ; // รับ InputStream จากซ็อกเก็ตไคลเอนต์
เครื่องอ่าน BufferedReader = BufferedReader ใหม่ (InputStreamReader ใหม่ (อินพุต));
สตริง nameLine = reader.readLine();
สตริง ageLine = reader.readLine();
สตริง emailLine= reader.readLine();
สตริง phoneLine= reader.readLine();
โปรดทราบว่าสถานะการประมวลผลจะพิจารณาจากระยะเวลาที่โปรแกรมดำเนินการ กล่าวอีกนัยหนึ่ง เมื่อเมธอด reader.readLine() กลับมา คุณจะทราบแน่ชัดว่าบรรทัดข้อความถูกอ่านแล้ว นี่คือสาเหตุที่ readline() บล็อกจนกว่าจะอ่านทั้งบรรทัด คุณรู้ด้วยว่าบรรทัดนี้มีชื่อในทำนองเดียวกัน เมื่อการเรียก readline() ครั้งที่สองกลับมา คุณจะรู้ว่าบรรทัดนี้มีอายุ ฯลฯ อย่างที่คุณเห็น ตัวจัดการนี้จะทำงานเมื่อมีการอ่านข้อมูลใหม่เท่านั้น และรู้ว่าข้อมูลนั้นคืออะไรในแต่ละขั้นตอน เมื่อเธรดที่รันอยู่ได้ประมวลผลข้อมูลบางส่วนที่ได้อ่านไปแล้ว เธรดนั้นจะไม่ย้อนกลับข้อมูล (ส่วนใหญ่) รูปต่อไปนี้ยังแสดงให้เห็นหลักการนี้ด้วย:
อ่านข้อมูลจากสตรีมที่ถูกบล็อก
แม้ว่าการนำ NIO ไปใช้งานจะแตกต่างออกไป นี่คือตัวอย่างง่ายๆ:
คัดลอกรหัสรหัสดังต่อไปนี้:
ByteBuffer buffer = ByteBuffer.allocate (48);
int bytesRead = inChannel.read (บัฟเฟอร์);
สังเกตบรรทัดที่สอง โดยอ่านไบต์จากช่องสัญญาณไปยัง ByteBuffer เมื่อการเรียกเมธอดนี้กลับมา คุณจะไม่รู้ว่าข้อมูลทั้งหมดที่คุณต้องการอยู่ในบัฟเฟอร์หรือไม่ สิ่งที่คุณรู้ก็คือบัฟเฟอร์มีไบต์จำนวนหนึ่ง ซึ่งทำให้การประมวลผลยากขึ้นเล็กน้อย
สมมติว่าหลังจากการเรียก read(buffer) ครั้งแรก ข้อมูลที่อ่านในบัฟเฟอร์มีเพียงครึ่งบรรทัด เช่น "ชื่อ: An" คุณสามารถประมวลผลข้อมูลได้หรือไม่ ไม่แน่นอน คุณต้องรอจนกว่าจะอ่านข้อมูลทั้งแถวลงในแคช ก่อนหน้านั้น การประมวลผลข้อมูลใดๆ จะไม่มีความหมาย
แล้วคุณจะรู้ได้อย่างไรว่าบัฟเฟอร์มีข้อมูลเพียงพอที่จะประมวลผลหรือไม่? คุณไม่รู้หรอก วิธีการค้นพบสามารถดูได้เฉพาะข้อมูลในบัฟเฟอร์เท่านั้น ผลลัพธ์คือคุณต้องตรวจสอบข้อมูลของบัฟเฟอร์หลายครั้งก่อนที่คุณจะรู้ว่าข้อมูลทั้งหมดอยู่ในบัฟเฟอร์ ไม่เพียงแต่จะไม่มีประสิทธิภาพเท่านั้น แต่ยังอาจทำให้โซลูชันการเขียนโปรแกรมเกะกะอีกด้วย ตัวอย่างเช่น:
คัดลอกรหัสรหัสดังต่อไปนี้:
ByteBuffer buffer = ByteBuffer.allocate (48);
int bytesRead = inChannel.read (บัฟเฟอร์);
ในขณะที่ (! bufferFull (bytesRead) ) {
bytesRead = inChannel.read (บัฟเฟอร์);
-
เมธอด bufferFull() จะต้องติดตามว่ามีการอ่านข้อมูลในบัฟเฟอร์มากน้อยเพียงใด และส่งคืนค่าจริงหรือเท็จ ขึ้นอยู่กับว่าบัฟเฟอร์เต็มหรือไม่ กล่าวอีกนัยหนึ่ง หากบัฟเฟอร์พร้อมที่จะประมวลผล บัฟเฟอร์จะเต็ม
เมธอด bufferFull() จะสแกนบัฟเฟอร์ แต่ต้องคงอยู่ในสถานะเดียวกับก่อนที่จะเรียกใช้เมธอด bufferFull() ถ้าไม่เช่นนั้น ข้อมูลถัดไปที่อ่านลงในบัฟเฟอร์อาจไม่ถูกอ่านไปยังตำแหน่งที่ถูกต้อง นี่เป็นไปไม่ได้ แต่ก็เป็นอีกประเด็นหนึ่งที่ต้องระวัง
หากบัฟเฟอร์เต็มก็สามารถประมวลผลได้ ถ้ามันใช้งานไม่ได้และสมเหตุสมผลในกรณีจริงของคุณ คุณอาจจะรับมือกับมันได้บางส่วน แต่ในหลายกรณีสิ่งนี้ไม่เป็นเช่นนั้น รูปต่อไปนี้แสดง "รอบข้อมูลบัฟเฟอร์พร้อม":
อ่านข้อมูลจากช่องสัญญาณจนกว่าข้อมูลทั้งหมดจะถูกอ่านลงในบัฟเฟอร์
สรุป
NIO ช่วยให้คุณจัดการหลายช่องทาง (การเชื่อมต่อเครือข่ายหรือไฟล์) โดยใช้เพียงเธรดเดียว (หรือสองสามรายการ) แต่ข้อดีคือการแยกวิเคราะห์ข้อมูลอาจซับซ้อนกว่าการอ่านข้อมูลจากสตรีมที่บล็อก
หากคุณต้องการจัดการการเชื่อมต่อหลายพันรายการที่เปิดพร้อมกันซึ่งส่งข้อมูลจำนวนเล็กน้อยในแต่ละครั้ง เช่น เซิร์ฟเวอร์แชท เซิร์ฟเวอร์ที่ใช้ NIO อาจเป็นข้อได้เปรียบ ในทำนองเดียวกัน หากคุณต้องการรักษาการเชื่อมต่อแบบเปิดจำนวนมากกับคอมพิวเตอร์เครื่องอื่น เช่น ในเครือข่าย P2P การใช้เธรดแยกต่างหากเพื่อจัดการการเชื่อมต่อขาออกทั้งหมดของคุณอาจเป็นข้อดี รูปแบบการออกแบบของการเชื่อมต่อหลายรายการในหนึ่งเธรดแสดงในรูปด้านล่าง:
เธรดเดี่ยวจัดการการเชื่อมต่อหลายรายการ
หากคุณมีการเชื่อมต่อจำนวนไม่มากที่ใช้แบนด์วิธที่สูงมาก และส่งข้อมูลจำนวนมากในคราวเดียว บางทีการใช้งานเซิร์ฟเวอร์ IO ทั่วไปอาจเหมาะสม รูปภาพต่อไปนี้แสดงให้เห็นการออกแบบเซิร์ฟเวอร์ IO ทั่วไป:
การออกแบบเซิร์ฟเวอร์ IO ทั่วไป:
การเชื่อมต่อถูกจัดการโดยเธรด
ช่อง
ช่อง Java NIO คล้ายกับสตรีม แต่แตกต่างกันบ้าง:
สามารถอ่านข้อมูลจากช่องและข้อมูลสามารถเขียนลงในช่องได้ แต่การอ่านและการเขียนมักจะเป็นแบบทางเดียว
ช่องสามารถอ่านและเขียนแบบอะซิงโครนัสได้
ข้อมูลในช่องจะต้องอ่านจากบัฟเฟอร์ก่อนหรือเขียนจากบัฟเฟอร์เสมอ
ตามที่กล่าวไว้ข้างต้น ข้อมูลจะถูกอ่านจากแชนเนลไปยังบัฟเฟอร์ และข้อมูลจะถูกเขียนจากบัฟเฟอร์ไปยังแชนเนล ดังที่แสดงด้านล่าง:
การใช้งานช่องทาง
นี่คือการใช้งานช่องทางที่สำคัญที่สุดใน Java NIO:
FileChannel: อ่านและเขียนข้อมูลจากไฟล์
DatagramChannel: สามารถอ่านและเขียนข้อมูลในเครือข่ายผ่าน UDP
SocketChannel: สามารถอ่านและเขียนข้อมูลในเครือข่ายผ่าน TCP
ServerSocketChannel: สามารถตรวจสอบการเชื่อมต่อ TCP ขาเข้าได้ เช่น เว็บเซิร์ฟเวอร์ SocketChannel ถูกสร้างขึ้นสำหรับการเชื่อมต่อขาเข้าใหม่แต่ละครั้ง
ตัวอย่างช่องพื้นฐาน
ต่อไปนี้เป็นตัวอย่างการใช้ FileChannel เพื่ออ่านข้อมูลลงใน Buffer:
คัดลอกรหัสรหัสดังต่อไปนี้:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate (48);
int bytesRead = inChannel.read(buf);
ในขณะที่ (bytesRead != -1) {
System.out.println("อ่าน" + bytesRead);
buf.พลิก();
ในขณะที่(buf.hasRemaining()){
System.out.print((ถ่าน) buf.get());
-
buf.ชัดเจน();
bytesRead = inChannel.read(buf);
-
aFile.ปิด();
โปรดทราบว่าการเรียก buf.flip() จะอ่านข้อมูลลงในบัฟเฟอร์ก่อน จากนั้นจึงย้อนกลับบัฟเฟอร์ จากนั้นจึงอ่านข้อมูลจากบัฟเฟอร์ ในส่วนถัดไปจะกล่าวถึงรายละเอียดเพิ่มเติมเกี่ยวกับบัฟเฟอร์
บัฟเฟอร์
บัฟเฟอร์ใน Java NIO ใช้เพื่อโต้ตอบกับช่อง NIO ดังที่คุณทราบ ข้อมูลจะถูกอ่านจากแชนเนลลงในบัฟเฟอร์ และเขียนจากบัฟเฟอร์ลงในแชนเนล
บัฟเฟอร์โดยพื้นฐานแล้วคือบล็อกของหน่วยความจำที่สามารถเขียนข้อมูลได้และจากข้อมูลที่สามารถอ่านได้ หน่วยความจำนี้ถูกจัดแพคเกจเป็นอ็อบเจ็กต์ NIO Buffer และมีชุดวิธีการเพื่อเข้าถึงหน่วยความจำนี้ได้อย่างสะดวก
การใช้งานบัฟเฟอร์เบื้องต้น
การใช้ Buffer เพื่ออ่านและเขียนข้อมูลโดยทั่วไปมีสี่ขั้นตอนต่อไปนี้:
เขียนข้อมูลลงในบัฟเฟอร์
โทรวิธีการพลิก ()
อ่านข้อมูลจากบัฟเฟอร์
เรียกเมธอด clear() หรือเมธอด compact()
เมื่อข้อมูลถูกเขียนลงในบัฟเฟอร์ บัฟเฟอร์จะบันทึกจำนวนข้อมูลที่ถูกเขียน เมื่อคุณต้องการอ่านข้อมูล คุณจะต้องเปลี่ยนบัฟเฟอร์จากโหมดเขียนเป็นโหมดอ่านโดยใช้เมธอด flip() ในโหมดอ่าน ข้อมูลทั้งหมดที่เขียนไปยังบัฟเฟอร์ก่อนหน้านี้สามารถอ่านได้
เมื่ออ่านข้อมูลทั้งหมดแล้ว จะต้องล้างบัฟเฟอร์เพื่อให้สามารถเขียนได้อีกครั้ง มีสองวิธีในการล้างบัฟเฟอร์: การเรียกเมธอด clear() หรือ Compact() clear() วิธีการล้างบัฟเฟอร์ทั้งหมด วิธี Compact() จะล้างเฉพาะข้อมูลที่อ่านแล้วเท่านั้น ข้อมูลที่ยังไม่ได้อ่านจะถูกย้ายไปยังจุดเริ่มต้นของบัฟเฟอร์ และข้อมูลที่เขียนใหม่จะถูกวางไว้หลังข้อมูลที่ยังไม่ได้อ่านในบัฟเฟอร์
นี่คือตัวอย่างการใช้บัฟเฟอร์:
คัดลอกรหัสรหัสดังต่อไปนี้:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//สร้างบัฟเฟอร์ที่มีความจุ 48 ไบต์
ByteBuffer buf = ByteBuffer.allocate (48);
int bytesRead = inChannel.read(buf); // อ่านลงในบัฟเฟอร์
ในขณะที่ (bytesRead != -1) {
buf.flip();//เตรียมบัฟเฟอร์ให้พร้อมสำหรับการอ่าน
ในขณะที่(buf.hasRemaining()){
System.out.print((char) buf.get()); // อ่านครั้งละ 1 ไบต์
-
buf.clear(); //เตรียมบัฟเฟอร์ให้พร้อมสำหรับการเขียน
bytesRead = inChannel.read(buf);
-
aFile.ปิด();
ความจุ ตำแหน่ง และขีดจำกัดของบัฟเฟอร์
บัฟเฟอร์โดยพื้นฐานแล้วคือบล็อกของหน่วยความจำที่สามารถเขียนข้อมูลได้และจากข้อมูลที่สามารถอ่านได้ หน่วยความจำนี้ถูกจัดแพคเกจเป็นอ็อบเจ็กต์ NIO Buffer และมีชุดวิธีการเพื่อเข้าถึงหน่วยความจำนี้ได้อย่างสะดวก
เพื่อให้เข้าใจถึงวิธีการทำงานของ Buffer คุณต้องทำความคุ้นเคยกับคุณสมบัติ 3 ประการของ Buffer:
ความจุ
ตำแหน่ง
ขีด จำกัด
ความหมายของตำแหน่งและขีดจำกัดขึ้นอยู่กับว่าบัฟเฟอร์อยู่ในโหมดอ่านหรือโหมดเขียน ไม่ว่าบัฟเฟอร์จะอยู่ในโหมดใดก็ตาม ความหมายของความจุจะเท่าเดิมเสมอ
ต่อไปนี้เป็นคำอธิบายเกี่ยวกับความจุ ตำแหน่ง และขีดจำกัดในโหมดอ่านและเขียน โดยมีคำอธิบายโดยละเอียดตามภาพประกอบ
ความจุ
เนื่องจากเป็นบล็อกหน่วยความจำ บัฟเฟอร์มีค่าขนาดคงที่ หรือที่เรียกว่า "ความจุ" คุณสามารถเขียนได้เฉพาะความจุของไบต์ ความยาว ถ่าน และประเภทอื่นๆ ลงไปเท่านั้น เมื่อบัฟเฟอร์เต็มแล้ว จะต้องล้างข้อมูล (โดยการอ่านข้อมูลหรือล้างข้อมูล) ก่อนจึงจะสามารถเขียนข้อมูลต่อได้
ตำแหน่ง
เมื่อคุณเขียนข้อมูลลงในบัฟเฟอร์ ตำแหน่ง แสดงถึงตำแหน่งปัจจุบัน ค่าตำแหน่งเริ่มต้นคือ 0 เมื่อมีการเขียนข้อมูลไบต์ ความยาว ฯลฯ ไปยังบัฟเฟอร์ ตำแหน่งจะเลื่อนไปข้างหน้าไปยังหน่วยบัฟเฟอร์ถัดไปซึ่งสามารถแทรกข้อมูลได้ ตำแหน่งสูงสุดสามารถเป็นความจุ 1
เมื่ออ่านข้อมูล ก็จะอ่านจากตำแหน่งเฉพาะด้วย เมื่อเปลี่ยนบัฟเฟอร์จากโหมดเขียนเป็นโหมดอ่าน ตำแหน่งจะถูกรีเซ็ตเป็น 0 เมื่ออ่านข้อมูลจากตำแหน่งของบัฟเฟอร์ ตำแหน่งจะเลื่อนไปข้างหน้าไปยังตำแหน่งที่อ่านได้ถัดไป
ขีด จำกัด
ในโหมดเขียน ขีดจำกัดของบัฟเฟอร์จะระบุจำนวนข้อมูลสูงสุดที่คุณสามารถเขียนไปยังบัฟเฟอร์ได้ ในโหมดเขียน ขีดจำกัดจะเท่ากับความจุของบัฟเฟอร์
เมื่อเปลี่ยนบัฟเฟอร์เป็นโหมดอ่าน ขีดจำกัดจะระบุจำนวนข้อมูลสูงสุดที่คุณสามารถอ่านได้ ดังนั้นเมื่อเปลี่ยนบัฟเฟอร์เป็นโหมดอ่าน ลิมิตจะถูกตั้งค่าเป็นค่าตำแหน่งในโหมดเขียน กล่าวอีกนัยหนึ่ง คุณสามารถอ่านข้อมูลทั้งหมดที่เขียนก่อนหน้านี้ได้ (ขีดจำกัดถูกกำหนดไว้ที่จำนวนข้อมูลที่เขียน ค่านี้อยู่ในตำแหน่งในโหมดเขียน)
ประเภทบัฟเฟอร์
Java NIO มีประเภทบัฟเฟอร์ดังต่อไปนี้:
ByteBuffer
แมป ByteBuffer
ชาร์บัฟเฟอร์
ดับเบิลบัฟเฟอร์
FloatBuffer
IntBuffer
ลองบัฟเฟอร์
ชอร์ตบัฟเฟอร์
อย่างที่คุณเห็น ประเภทบัฟเฟอร์เหล่านี้แสดงถึงประเภทข้อมูลที่แตกต่างกัน กล่าวอีกนัยหนึ่ง ไบต์ในบัฟเฟอร์สามารถจัดการผ่านประเภท char, short, int, long, float หรือ double
MappedByteBuffer มีความพิเศษเล็กน้อย และจะมีการพูดคุยกันในบทของมันเอง
การจัดสรรบัฟเฟอร์
เมื่อต้องการรับวัตถุบัฟเฟอร์ คุณต้องจัดสรรมันก่อน ทุกคลาสบัฟเฟอร์มีวิธีจัดสรร ด้านล่างนี้เป็นตัวอย่างของการจัดสรร ByteBuffer ที่มีความจุ 48 ไบต์
คัดลอกรหัสรหัสดังต่อไปนี้:
ByteBuffer buf = ByteBuffer.allocate (48);
สิ่งนี้จัดสรร CharBuffer ที่สามารถจัดเก็บอักขระได้ 1,024 ตัว:
คัดลอกรหัสรหัสดังต่อไปนี้:
CharBuffer buf = CharBuffer.จัดสรร (1024);
เขียนข้อมูลลงในบัฟเฟอร์
มีสองวิธีในการเขียนข้อมูลลงบัฟเฟอร์:
เขียนจาก Channel ถึง Buffer
เขียนบัฟเฟอร์ผ่านวิธี put() ของ Buffer
ตัวอย่างการเขียนจาก Channel ถึง Buffer
คัดลอกรหัสรหัสดังต่อไปนี้:
int bytesRead = inChannel.read(buf); // อ่านลงในบัฟเฟอร์
ตัวอย่างการเขียนบัฟเฟอร์ด้วยวิธีใส่:
คัดลอกรหัสรหัสดังต่อไปนี้:
buf.ใส่(127);
มีวิธี put อยู่หลายเวอร์ชัน ซึ่งช่วยให้คุณสามารถเขียนข้อมูลไปยัง Buffer ได้หลายวิธี ตัวอย่างเช่น การเขียนไปยังตำแหน่งที่ระบุ หรือการเขียนอาร์เรย์ไบต์ไปยังบัฟเฟอร์ สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการใช้งานบัฟเฟอร์ โปรดดูที่ JavaDoc
วิธีการพลิก ()
วิธีการพลิกจะสลับบัฟเฟอร์จากโหมดเขียนเป็นโหมดอ่าน การเรียกเมธอด flip() จะกำหนดตำแหน่งกลับเป็น 0 และกำหนดขีดจำกัดให้เป็นค่าของตำแหน่งก่อนหน้า
กล่าวอีกนัยหนึ่ง ตอนนี้ตำแหน่งถูกใช้เพื่อทำเครื่องหมายตำแหน่งการอ่าน และขีดจำกัดแสดงถึงจำนวนไบต์ ตัวอักษร ฯลฯ ที่ถูกเขียนก่อนหน้านี้ - จำนวนไบต์ ตัวอักษร ฯลฯ ที่สามารถอ่านได้ในขณะนี้
อ่านข้อมูลจากบัฟเฟอร์
มีสองวิธีในการอ่านข้อมูลจาก Buffer:
อ่านข้อมูลจาก Buffer ไปยัง Channel
ใช้เมธอด get() เพื่ออ่านข้อมูลจาก Buffer
ตัวอย่างการอ่านข้อมูลจาก Buffer ไปยัง Channel:
คัดลอกรหัสรหัสดังต่อไปนี้:
//อ่านจากบัฟเฟอร์เข้าสู่ช่อง
int bytesWritten = inChannel.write(buf);
ตัวอย่างการใช้เมธอด get() เพื่ออ่านข้อมูลจาก Buffer
คัดลอกรหัสรหัสดังต่อไปนี้:
ไบต์ aByte = buf.get();
มีเมธอด get อยู่หลายเวอร์ชัน ซึ่งช่วยให้คุณอ่านข้อมูลจาก Buffer ได้หลายวิธี ตัวอย่างเช่น อ่านจากตำแหน่งที่ระบุ หรืออ่านข้อมูลจากบัฟเฟอร์ลงในอาร์เรย์ไบต์ สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการใช้งานบัฟเฟอร์ โปรดดูที่ JavaDoc
วิธีการย้อนกลับ ()
Buffer.rewind() ตั้งค่าตำแหน่งกลับเป็น 0 เพื่อให้คุณสามารถอ่านข้อมูลทั้งหมดใน Buffer ได้ ขีดจำกัดยังคงไม่เปลี่ยนแปลงและยังคงระบุจำนวนองค์ประกอบ (ไบต์ ถ่าน ฯลฯ) ที่สามารถอ่านได้จากบัฟเฟอร์
วิธีการ clear() และ Compact()
เมื่ออ่านข้อมูลในบัฟเฟอร์แล้ว บัฟเฟอร์จะต้องพร้อมที่จะเขียนอีกครั้ง ซึ่งสามารถทำได้โดยใช้เมธอด clear() หรือ compact()
หากเรียกใช้เมธอด clear() ตำแหน่งจะถูกตั้งค่ากลับเป็น 0 และขีดจำกัดจะถูกตั้งค่าเป็นค่าความจุ กล่าวอีกนัยหนึ่งบัฟเฟอร์จะถูกล้างออกไป ข้อมูลในบัฟเฟอร์ไม่ได้ถูกล้าง แต่เครื่องหมายเหล่านี้บอกเราว่าจะเริ่มเขียนข้อมูลลงในบัฟเฟอร์ได้ที่ไหน
หากมีข้อมูลที่ยังไม่ได้อ่านใน Buffer และคุณเรียกใช้เมธอด clear() ข้อมูลนั้นจะถูก "ลืม" ซึ่งหมายความว่าจะไม่มีเครื่องหมายใดๆ ที่จะบอกคุณอีกต่อไปว่าข้อมูลใดถูกอ่านและข้อมูลใดที่ยังไม่ได้อ่าน
หากยังมีข้อมูลที่ยังไม่ได้อ่านใน Buffer และจำเป็นต้องใช้ข้อมูลในภายหลัง แต่คุณต้องการเขียนข้อมูลบางส่วนก่อน ให้ใช้เมธอด compact()
วิธีการกระชับ () จะคัดลอกข้อมูลที่ยังไม่ได้อ่านทั้งหมดไปยังจุดเริ่มต้นของบัฟเฟอร์ จากนั้นตั้งค่าตำแหน่งให้อยู่ด้านหลังองค์ประกอบที่ยังไม่ได้อ่านล่าสุด คุณลักษณะขีดจำกัดยังคงถูกตั้งค่าเป็นความจุเหมือนกับเมธอด clear() ขณะนี้บัฟเฟอร์พร้อมสำหรับการเขียนข้อมูลแล้ว แต่ข้อมูลที่ยังไม่ได้อ่านจะไม่ถูกเขียนทับ
วิธีการทำเครื่องหมาย () และรีเซ็ต ()
ด้วยการเรียกเมธอด Buffer.mark() คุณสามารถทำเครื่องหมายตำแหน่งเฉพาะใน Buffer ได้ คุณสามารถคืนค่าไปยังตำแหน่งนี้ได้ในภายหลังโดยการเรียกเมธอด Buffer.reset() ตัวอย่างเช่น:
คัดลอกรหัสรหัสดังต่อไปนี้:
buffer.mark();
//เรียก buffer.get() สองสามครั้ง เช่น ระหว่างการแยกวิเคราะห์
buffer.reset();//กำหนดตำแหน่งกลับไปทำเครื่องหมาย
เท่ากับ() และ comparisonTo() วิธีการ
คุณสามารถใช้เมธอดเท่ากับ () และ CompareTo() สำหรับบัฟเฟอร์สองตัว
เท่ากับ()
เมื่อตรงตามเงื่อนไขต่อไปนี้ หมายความว่าบัฟเฟอร์ทั้งสองมีค่าเท่ากัน:
มีประเภทเดียวกัน (ไบต์, ถ่าน, int ฯลฯ)
จำนวนไบต์ ตัวอักษร ฯลฯ ที่เหลืออยู่ในบัฟเฟอร์จะเท่ากัน
ไบต์ ตัวอักษร ฯลฯ ที่เหลือทั้งหมดในบัฟเฟอร์จะเหมือนกัน
อย่างที่คุณเห็น เท่ากับเปรียบเทียบเฉพาะส่วนของบัฟเฟอร์ ไม่ใช่ทุกองค์ประกอบในนั้น ในความเป็นจริงจะเปรียบเทียบเฉพาะองค์ประกอบที่เหลืออยู่ในบัฟเฟอร์เท่านั้น
วิธีการเปรียบเทียบ To()
เมธอด CompareTo() จะเปรียบเทียบองค์ประกอบที่เหลือ (ไบต์ ถ่าน ฯลฯ) ของบัฟเฟอร์สองตัว หากตรงตามเงื่อนไขต่อไปนี้ บัฟเฟอร์หนึ่งจะถือว่า "น้อยกว่า" บัฟเฟอร์อื่น:
องค์ประกอบที่ไม่เท่ากันตัวแรกมีขนาดเล็กกว่าองค์ประกอบที่สอดคล้องกันในบัฟเฟอร์อื่น
องค์ประกอบทั้งหมดเท่ากัน แต่บัฟเฟอร์แรกจะหมดก่อนองค์ประกอบอื่น (บัฟเฟอร์แรกมีองค์ประกอบน้อยกว่าองค์ประกอบอื่น)
(คำอธิบายประกอบ: องค์ประกอบที่เหลือคือองค์ประกอบจากตำแหน่งจนถึงขีดจำกัด)
กระจาย/รวบรวม
(ที่อยู่เดิมของส่วนนี้ ผู้แต่ง: Jakob Jenkov ผู้แปล: Guo Lei)
Java NIO เริ่มรองรับ Scatter/gather Scatter/gather ใช้เพื่ออธิบายการดำเนินการอ่านหรือเขียนไปยัง Channel (หมายเหตุผู้แปล: Channel มักแปลเป็น channel ในภาษาจีน)
การอ่านแบบกระจายจากช่องหมายถึงการเขียนข้อมูลที่อ่านลงในบัฟเฟอร์หลายตัวระหว่างการดำเนินการอ่าน ดังนั้นแชนเนลจึง "กระจาย" ข้อมูลที่อ่านจากแชนเนลไปยังบัฟเฟอร์หลายตัว
การรวบรวมและเขียนลงในแชนเนลหมายถึงการเขียนข้อมูลจากบัฟเฟอร์หลายตัวไปยังแชนเนลเดียวกันระหว่างการดำเนินการเขียน ดังนั้น แชนเนลจึง "รวบรวม" ข้อมูลในบัฟเฟอร์หลายตัวและส่งไปยังแชนเนล
Scatter/gather มักใช้ในสถานการณ์ที่ข้อมูลที่ส่งต้องได้รับการประมวลผลแยกกัน ตัวอย่างเช่น เมื่อส่งข้อความที่ประกอบด้วยส่วนหัวของข้อความและเนื้อหาข้อความ คุณอาจกระจายเนื้อหาข้อความและส่วนหัวของข้อความไปยังบัฟเฟอร์ที่แตกต่างกัน ดังนั้น คุณสามารถประมวลผลส่วนหัวของข้อความและเนื้อหาข้อความได้อย่างสะดวก
การอ่านแบบกระจาย
การอ่านแบบกระจายหมายถึงการอ่านข้อมูลจากช่องหนึ่งไปยังหลายบัฟเฟอร์ ตามที่อธิบายไว้ในภาพด้านล่าง:
ตัวอย่างโค้ดมีดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
ส่วนหัว ByteBuffer = ByteBuffer.allocate (128);
ByteBuffer body = ByteBuffer.allocate (1024);
ByteBuffer[] bufferArray = { ส่วนหัว, เนื้อความ };
ช่อง.อ่าน(bufferArray);
โปรดทราบว่าบัฟเฟอร์จะถูกแทรกลงในอาร์เรย์ก่อน จากนั้นจึงใช้อาร์เรย์เป็นพารามิเตอร์อินพุตสำหรับ channel.read() เมธอด read() จะเขียนข้อมูลที่อ่านจากช่องสัญญาณไปยังบัฟเฟอร์ตามลำดับบัฟเฟอร์ในอาร์เรย์ เมื่อบัฟเฟอร์ตัวหนึ่งถูกเติม ช่องสัญญาณจะเขียนไปยังบัฟเฟอร์อื่น
การอ่านแบบกระจายจะต้องเติมบัฟเฟอร์ปัจจุบันก่อนที่จะย้ายไปยังบัฟเฟอร์ถัดไป ซึ่งหมายความว่ามันไม่เหมาะสำหรับข้อความไดนามิก (หมายเหตุผู้แปล: ขนาดข้อความไม่ได้รับการแก้ไข) กล่าวอีกนัยหนึ่ง หากมีส่วนหัวของข้อความและเนื้อหาข้อความ ส่วนหัวของข้อความจะต้องเต็ม (เช่น 128 ไบต์) เพื่อให้ Scattering Read ทำงานได้อย่างถูกต้อง
รวบรวมงานเขียน
การรวบรวมการเขียนหมายความว่าข้อมูลถูกเขียนจากบัฟเฟอร์หลายตัวไปยังช่องสัญญาณเดียวกัน ตามที่อธิบายไว้ในภาพด้านล่าง:
ตัวอย่างโค้ดมีดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
ส่วนหัว ByteBuffer = ByteBuffer.allocate (128);
ByteBuffer body = ByteBuffer.allocate (1024);
//เขียนข้อมูลลงในบัฟเฟอร์
ByteBuffer[] bufferArray = { ส่วนหัว, เนื้อความ };
ช่อง.เขียน(bufferArray);
อาร์เรย์บัฟเฟอร์เป็นพารามิเตอร์อินพุตของเมธอด write() จะเขียนข้อมูลไปยังช่องสัญญาณตามลำดับบัฟเฟอร์ในอาร์เรย์ โปรดทราบว่าเฉพาะข้อมูลระหว่างตำแหน่งและขีดจำกัดเท่านั้นที่จะถูกเขียน ดังนั้นหากบัฟเฟอร์มีความจุ 128 ไบต์ แต่มีข้อมูลเพียง 58 ไบต์ ข้อมูล 58 ไบต์จะถูกเขียนลงในแชนเนล ดังนั้น ตรงกันข้ามกับการอ่านแบบกระจาย การรวบรวมการเขียนสามารถจัดการข้อความไดนามิกได้ดีขึ้น
การถ่ายโอนข้อมูลระหว่างช่อง
(ที่อยู่เดิมของส่วนนี้ ผู้แต่ง: Jakob Jenkov ผู้แปล: Guo Lei ผู้พิสูจน์อักษร: Zhou Tai)
ใน Java NIO หากหนึ่งในสองช่องสัญญาณเป็น FileChannel คุณสามารถถ่ายโอนข้อมูลจากช่องหนึ่งได้โดยตรง (หมายเหตุของผู้แปล: ช่องมักแปลเป็นช่องในภาษาจีน) ไปยังช่องอื่น
โอนจาก()
เมธอด TransferFrom() ของ FileChannel สามารถถ่ายโอนข้อมูลจากช่องสัญญาณต้นทางไปยัง FileChannel (หมายเหตุของนักแปล: วิธีการนี้อธิบายไว้ในเอกสารประกอบ JDK ว่าเป็นการถ่ายโอนไบต์จากช่องไบต์ที่อ่านได้ที่กำหนดไปยังไฟล์ของช่องนี้) นี่เป็นตัวอย่างง่ายๆ:
คัดลอกรหัสรหัสดังต่อไปนี้:
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
ตำแหน่งยาว = 0;
จำนวนยาว = fromChannel.size();
toChannel.transferFrom(ตำแหน่ง, นับ, fromChannel);
ตำแหน่งพารามิเตอร์อินพุตของเมธอดบ่งชี้การเริ่มต้นจากตำแหน่งที่จะเขียนข้อมูลไปยังไฟล์เป้าหมาย และการนับบ่งชี้จำนวนไบต์สูงสุดที่ถ่ายโอน หากช่องต้นทางมีพื้นที่เหลือน้อยกว่าจำนวนไบต์ จำนวนไบต์ที่ถ่ายโอนจะน้อยกว่าจำนวนไบต์ที่ร้องขอ
นอกจากนี้ควรสังเกตว่าในการใช้งาน SoketChannel นั้น SocketChannel จะส่งข้อมูลที่เตรียมไว้ในขณะนี้เท่านั้น (ซึ่งอาจน้อยกว่าจำนวนไบต์) ดังนั้น SocketChannel อาจไม่ถ่ายโอนข้อมูลที่ร้องขอทั้งหมด (จำนวนไบต์) ไปยัง FileChannel
โอนไปยัง()
TransferTo() วิธีการโอนข้อมูลจาก FileChannel ไปยังช่องทางอื่น นี่เป็นตัวอย่างง่ายๆ:
คัดลอกรหัสรหัสดังต่อไปนี้:
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
ตำแหน่งยาว = 0;
จำนวนยาว = fromChannel.size();
fromChannel.transferTo (ตำแหน่ง, นับ, ถึง Channel);
คุณพบว่าตัวอย่างนี้คล้ายกับตัวอย่างก่อนหน้าเป็นพิเศษหรือไม่ ยกเว้นว่าวัตถุ FileChannel ที่เรียกใช้เมธอดนั้นแตกต่างกัน ทุกอย่างก็เหมือนกัน
ปัญหาที่กล่าวถึงข้างต้นเกี่ยวกับ SocketChannel ยังมีอยู่ในเมธอด TransferTo() SocketChannel จะยังคงส่งข้อมูลต่อไปจนกว่าบัฟเฟอร์เป้าหมายจะเต็ม
ตัวเลือก
(ลิงก์ไปยังข้อความต้นฉบับของหัวข้อนี้ ผู้แต่ง: Jakob Jenkov ผู้แปล: Langjiv ผู้พิสูจน์อักษร: Ding Yi)
Selector เป็นส่วนประกอบใน Java NIO ที่สามารถตรวจจับช่อง NIO ตั้งแต่หนึ่งช่องขึ้นไป และรู้ว่าช่องพร้อมสำหรับเหตุการณ์ต่างๆ เช่น การอ่านและเขียนหรือไม่ ด้วยวิธีนี้ เธรดเดียวสามารถจัดการหลายช่องสัญญาณและการเชื่อมต่อเครือข่ายหลายรายการได้
(1) เหตุใดจึงต้องใช้ตัวเลือก?
ข้อดีของการใช้เพียงเธรดเดียวในการจัดการหลายแชนเนลก็คือ ต้องใช้เธรดน้อยลงในการจัดการแชนเนล ในความเป็นจริง คุณสามารถใช้เธรดเดียวเพื่อจัดการทุกช่องสัญญาณได้ สำหรับระบบปฏิบัติการ การสลับบริบทระหว่างเธรดมีราคาแพงมาก และแต่ละเธรดใช้ทรัพยากรระบบบางส่วน (เช่น หน่วยความจำ) ดังนั้นยิ่งใช้เธรดน้อยลงก็ยิ่งดีเท่านั้น
อย่างไรก็ตาม โปรดทราบว่าระบบปฏิบัติการและ CPU สมัยใหม่กำลังดีขึ้นเรื่อยๆ ในการทำงานหลายอย่างพร้อมกัน ดังนั้นค่าใช้จ่ายของการทำงานแบบมัลติเธรดจึงน้อยลงเรื่อยๆ เมื่อเวลาผ่านไป ที่จริงแล้ว หาก CPU มีหลายคอร์ การไม่ใช้งานมัลติทาสก์อาจทำให้สิ้นเปลืองพลังงานของ CPU อย่างไรก็ตาม การอภิปรายเกี่ยวกับการออกแบบนั้นควรอยู่ในบทความอื่น ในที่นี้ ก็เพียงพอแล้วที่จะรู้ว่าคุณสามารถจัดการหลายช่องสัญญาณโดยใช้ตัวเลือกได้
ต่อไปนี้คือไดอะแกรมตัวอย่างของเธรดเดี่ยวโดยใช้ Selector เพื่อประมวลผลสามแชนเนล:
(2) การสร้างตัวเลือก
สร้างตัวเลือกโดยการเรียกเมธอด Selector.open() ดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
ตัวเลือก ตัวเลือก = Selector.open();
(3) ลงทะเบียนช่องด้วย Selector
หากต้องการใช้ Channel และ Selector ร่วมกัน ต้องลงทะเบียนช่องกับ Selector ก่อน ซึ่งสามารถทำได้โดยใช้เมธอด SelectableChannel.register() ดังต่อไปนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
channel.configureBlocking (เท็จ);
ปุ่ม SelectionKey = channel.register (ตัวเลือก,
Selectionkey.OP_READ);
เมื่อใช้ร่วมกับ Selector ช่องจะต้องอยู่ในโหมดไม่ปิดกั้น ซึ่งหมายความว่าคุณไม่สามารถใช้ FileChannel กับ Selector ได้เนื่องจาก FileChannel ไม่สามารถเปลี่ยนเป็นโหมดที่ไม่มีการบล็อกได้ ช่องซ็อกเก็ตก็โอเค
สังเกตพารามิเตอร์ตัวที่สองของเมธอด register() นี่คือ "การรวบรวมความสนใจ" ซึ่งหมายถึงเหตุการณ์ใดที่คุณสนใจเมื่อฟังช่องผ่านตัวเลือก มีเหตุการณ์สี่ประเภทที่แตกต่างกันที่สามารถฟังได้:
เชื่อมต่อ
ยอมรับ
อ่าน
เขียน
ช่องที่ทำให้เกิดเหตุการณ์หมายความว่าเหตุการณ์พร้อมแล้ว ดังนั้นช่องทางที่เชื่อมต่อกับเซิร์ฟเวอร์อื่นได้สำเร็จจึงเรียกว่า "การเชื่อมต่อพร้อม" ช่องซ็อกเก็ตเซิร์ฟเวอร์กล่าวว่า "พร้อมที่จะรับ" เมื่อพร้อมที่จะรับการเชื่อมต่อขาเข้า ช่องที่มีข้อมูลให้อ่านเรียกว่า "พร้อม" ช่องทางที่รอการเขียนข้อมูลอาจกล่าวได้ว่า "พร้อมเขียน"
เหตุการณ์ทั้งสี่นี้แสดงด้วยค่าคงที่สี่ค่าของ SelectionKey:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
หากคุณสนใจมากกว่าหนึ่งเหตุการณ์ คุณสามารถใช้ตัวดำเนินการระดับบิต OR เพื่อเชื่อมต่อค่าคงที่ได้ดังต่อไปนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
int interestSet = SelectionKey.OP_READ |. SelectionKey.OP_WRITE;
การรวบรวมดอกเบี้ยจะกล่าวถึงด้านล่าง
(4)คีย์การเลือก
ในส่วนก่อนหน้านี้ เมื่อลงทะเบียน Channel ด้วย Selector เมธอด register() จะส่งคืนออบเจ็กต์ SelectionKey วัตถุนี้มีคุณสมบัติบางอย่างที่คุณอาจสนใจ:
การเก็บดอกเบี้ย
พร้อมคอลเลกชัน
ช่อง
ตัวเลือก
ออบเจ็กต์เพิ่มเติม (ไม่บังคับ)
ด้านล่างนี้ฉันอธิบายคุณสมบัติเหล่านี้
การเก็บดอกเบี้ย
ตามที่อธิบายไว้ในส่วนการลงทะเบียนช่องด้วยตัวเลือก การรวบรวมความสนใจคือคอลเลกชันของกิจกรรมที่น่าสนใจที่คุณเลือก คุณสามารถอ่านและเขียนการรวบรวมความสนใจผ่าน SelectionKey ได้ดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
int interestSet = SelectionKey.interess();
บูลีน isInterestedInAccept= (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
บูลีน isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
บูลีน isInterestedInRead = interestSet & SelectionKey.OP_READ;
บูลีน isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
จะเห็นได้ว่าการใช้ "bit AND" เพื่อดำเนินการรวบรวมดอกเบี้ยและค่าคงที่ SelectionKey ที่กำหนด คุณจะสามารถกำหนดได้ว่ามีเหตุการณ์ใดเหตุการณ์หนึ่งอยู่ในการรวบรวมดอกเบี้ยหรือไม่
พร้อมคอลเลกชัน
ชุดพร้อมคือชุดการดำเนินการที่ช่องสัญญาณพร้อม หลังจากเลือก (Selection) คุณจะเข้าถึงชุดที่พร้อมก่อน การคัดเลือกจะอธิบายไว้ในส่วนถัดไป สามารถเข้าถึงคอลเลกชันที่พร้อมได้ดังนี้:
int readySet = SelectKey.readyOps();
คุณสามารถใช้วิธีการเดียวกันกับการตรวจจับการรวบรวมความสนใจเพื่อตรวจสอบว่าเหตุการณ์หรือการดำเนินการใดบ้างที่พร้อมในช่องทาง อย่างไรก็ตาม มีสี่วิธีต่อไปนี้ให้เลือก ซึ่งทั้งหมดจะส่งกลับประเภทบูลีน:
คัดลอกรหัสรหัสดังต่อไปนี้:
SelectKey.isAcceptable();
SelectKey.isConnectable();
SelectKey.isReadable();
SelectKey.isWritable();
ช่อง+ตัวเลือก
การเข้าถึง Channel และ Selector จาก SelectionKey นั้นง่ายดาย ดังต่อไปนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
ช่องช่อง= SelectKey.ช่อง();
ตัวเลือก ตัวเลือก = SelectionKey.selector();
วัตถุเพิ่มเติม
สามารถแนบออบเจ็กต์หรือข้อมูลเพิ่มเติมเข้ากับ SelectionKey เพื่อระบุช่องที่กำหนดได้อย่างง่ายดาย ตัวอย่างเช่น คุณสามารถแนบบัฟเฟอร์เพื่อใช้กับช่องสัญญาณหรือออบเจ็กต์ที่มีข้อมูลที่รวบรวมไว้ได้ วิธีใช้:
คัดลอกรหัสรหัสดังต่อไปนี้:
SelectKey.แนบ (วัตถุ);
วัตถุที่แนบมาObj = SelectKey.สิ่งที่แนบมา();
คุณยังสามารถแนบอ็อบเจ็กต์ได้เมื่อลงทะเบียน Channel ด้วย Selector โดยใช้เมธอด register() ชอบ:
คัดลอกรหัสรหัสดังต่อไปนี้:
ปุ่ม SelectionKey = channel.register (ตัวเลือก, SelectionKey.OP_READ, theObject);
(5) เลือกช่องผ่าน Selector
เมื่อลงทะเบียนช่องสัญญาณตั้งแต่หนึ่งช่องขึ้นไปด้วย Selector แล้ว คุณสามารถเรียกใช้เมธอด select() ที่โอเวอร์โหลดได้หลายวิธี วิธีการเหล่านี้จะส่งคืนช่องทางที่พร้อมสำหรับกิจกรรมที่คุณสนใจ (เช่น เชื่อมต่อ ยอมรับ อ่าน หรือเขียน) กล่าวอีกนัยหนึ่ง หากคุณสนใจแชนเนล "พร้อมอ่าน" เมธอด select() จะส่งคืนแชนเนลเหล่านั้นซึ่งมีกิจกรรมการอ่านพร้อมอยู่
นี่คือวิธีการเลือก ():
int เลือก()
int เลือก (หมดเวลานาน)
int เลือกตอนนี้()
select() บล็อกจนกว่าอย่างน้อยหนึ่งช่องจะพร้อมสำหรับกิจกรรมที่คุณลงทะเบียน
select(long timeout) เหมือนกับ select() ยกเว้นว่าจะบล็อกการหมดเวลาเป็นมิลลิวินาที (พารามิเตอร์)
selectNow() จะไม่บล็อกและส่งคืนทันทีไม่ว่าช่องใดจะพร้อม (หมายเหตุผู้แปล: วิธีการนี้จะดำเนินการเลือกแบบไม่บล็อก หากไม่มีช่องใดให้เลือกนับตั้งแต่การดำเนินการเลือกครั้งก่อน วิธีการนี้จะส่งคืนศูนย์โดยตรง)
ค่า int ที่ส่งคืนโดย select() วิธีการระบุจำนวนช่องสัญญาณที่พร้อม นั่นคือจำนวนช่องสัญญาณที่พร้อมนับตั้งแต่การเรียกเมธอด select() ครั้งล่าสุด หากเรียกใช้เมธอด select() จะมีการส่งคืน 1 เนื่องจากช่องหนึ่งพร้อมแล้ว หากเรียกใช้เมธอด select() อีกครั้ง หากช่องอื่นพร้อมก็จะส่งคืน 1 อีกครั้ง หากไม่มีการดำเนินการกับแชนเนลที่พร้อมใช้งานแรก ขณะนี้มีสองแชนเนลที่พร้อม แต่ระหว่างการเรียกเมธอด select() แต่ละครั้ง จะมีเพียงแชนเนลเดียวเท่านั้นที่พร้อม
คีย์ที่เลือก()
เมื่อเรียกใช้เมธอด select() และค่าที่ส่งคืนบ่งชี้ว่าหนึ่งช่องขึ้นไปพร้อมแล้ว คุณจะสามารถเข้าถึงช่องที่พร้อมใช้งานใน "ชุดคีย์ที่เลือก" ได้โดยการเรียกเมธอด SelectedKeys() ของตัวเลือก ดังที่แสดงด้านล่าง:
คัดลอกรหัสรหัสดังต่อไปนี้:
ตั้งค่า SelectedKeys = selector.selectedKeys();
เมื่อลงทะเบียน Channel เช่น Selector วิธีการ Channel.register() จะส่งกลับวัตถุ SelectionKey วัตถุนี้แสดงถึงช่องที่ลงทะเบียนกับตัวเลือก วัตถุเหล่านี้สามารถเข้าถึงได้ผ่านเมธอด SelectionKeySet() ของ SelectionKey
สามารถเข้าถึงช่องสัญญาณที่พร้อมใช้งานได้โดยผ่านชุดคีย์ที่เลือกนี้ ดังต่อไปนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
ตั้งค่า SelectedKeys = selector.selectedKeys();
ตัววนซ้ำ keyIterator = SelectKeys.iterator();
ในขณะที่ (keyIterator.hasNext ()) {
ปุ่ม SelectionKey = keyIterator.next();
ถ้า (key.isAcceptable ()) {
// การเชื่อมต่อได้รับการยอมรับโดย ServerSocketChannel
} อื่นถ้า (key.isConnectable()) {
// สร้างการเชื่อมต่อกับเซิร์ฟเวอร์ระยะไกล
} อื่นถ้า (key.isReadable()) {
// ช่องพร้อมให้อ่านแล้ว
} อื่น ๆ ถ้า (key.isWritable()) {
//ช่องพร้อมเขียนแล้ว
-
keyIterator.<tuihighlight><a href="javascript:;" style="display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text- decoration:underline;">ลบ</a ></tuiไฮไลท์>();
-
การวนซ้ำนี้จะวนซ้ำแต่ละคีย์ในชุดคีย์ที่เลือก และตรวจพบเหตุการณ์ที่พร้อมสำหรับช่องสัญญาณที่สอดคล้องกับแต่ละคีย์
สังเกตการเรียก keyIterator.remove() เมื่อสิ้นสุดการวนซ้ำแต่ละครั้ง ตัวเลือกจะไม่ลบอินสแตนซ์ SelectionKey ออกจากชุดคีย์ที่เลือกเอง ต้องลบตัวเองออกเมื่อช่องได้รับการประมวลผล ครั้งถัดไปที่ช่องพร้อม ตัวเลือกจะใส่ลงในชุดคีย์ที่เลือกอีกครั้ง
ช่องสัญญาณที่ส่งคืนโดยเมธอด SelectionKey.channel() จะต้องถูกแปลงเป็นประเภทที่คุณต้องการประมวลผล เช่น ServerSocketChannel หรือ SocketChannel เป็นต้น
(6)การปลุก()
เธรดถูกบล็อกหลังจากเรียกเมธอด select() แม้ว่าจะไม่มีแชนเนลใดที่พร้อม แต่ก็ยังมีวิธีส่งคืนจากเมธอด select() เพียงแค่ปล่อยให้เธรดอื่นเรียกใช้เมธอด Selector.wakeup() บนออบเจ็กต์โดยที่เธรดแรกเรียกว่าเมธอด select() เธรดที่ถูกบล็อกในเมธอด select() จะกลับมาทันที
หากเธรดอื่นเรียกใช้เมธอด wakeup() แต่ไม่มีเธรดใดถูกบล็อกในเมธอด select() เธรดถัดไปที่เรียกใช้เมธอด select() จะ "ปลุก" ทันที
(7)ปิด()
การเรียกเมธอด close() หลังจากใช้ Selector จะเป็นการปิด Selector และทำให้อินสแตนซ์ SelectionKey ทั้งหมดที่ลงทะเบียนกับ Selector เป็นโมฆะ ช่องเองก็ไม่ได้ปิด
(8) ตัวอย่างที่สมบูรณ์
นี่คือตัวอย่างที่สมบูรณ์ เปิด Selector ลงทะเบียนช่องสัญญาณกับ Selector (ละเว้นกระบวนการเริ่มต้นของช่องสัญญาณ) จากนั้นตรวจสอบอย่างต่อเนื่องว่าเหตุการณ์ทั้งสี่ของ Selector (ยอมรับ เชื่อมต่อ อ่าน เขียน) พร้อมหรือไม่
คัดลอกรหัสรหัสดังต่อไปนี้:
ตัวเลือก ตัวเลือก = Selector.open();
channel.configureBlocking (เท็จ);
ปุ่ม SelectionKey = channel.register (ตัวเลือก, SelectionKey.OP_READ);
ในขณะที่ (จริง) {
int readyChannels = selector.select();
ถ้า (readyChannels == 0) ดำเนินการต่อ;
ตั้งค่า SelectedKeys = selector.selectedKeys();
ตัววนซ้ำ keyIterator = SelectKeys.iterator();
ในขณะที่ (keyIterator.hasNext ()) {
ปุ่ม SelectionKey = keyIterator.next();
ถ้า (key.isAcceptable ()) {
// การเชื่อมต่อได้รับการยอมรับโดย ServerSocketChannel
} อื่นถ้า (key.isConnectable()) {
// สร้างการเชื่อมต่อกับเซิร์ฟเวอร์ระยะไกล
} อื่นถ้า (key.isReadable()) {
// ช่องพร้อมให้อ่านแล้ว
} อื่น ๆ ถ้า (key.isWritable()) {
//ช่องพร้อมเขียนแล้ว
-
keyIterator.<tuihighlight><a href="javascript:;" style="display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text- decoration:underline;">ลบ</a ></tuiไฮไลท์>();
-
-
ช่องไฟล์
(ลิงก์ไปยังข้อความต้นฉบับของหัวข้อนี้ ผู้แต่ง: Jakob Jenkov ผู้แปล: Zhou Tai ผู้พิสูจน์อักษร: Ding Yi)
FileChannel ใน Java NIO เป็นช่องที่เชื่อมต่อกับไฟล์ ไฟล์สามารถอ่านและเขียนผ่านช่องทางไฟล์
FileChannel ไม่สามารถตั้งค่าเป็นโหมดไม่บล็อกได้ แต่จะทำงานในโหมดบล็อกเสมอ
OpenFileChannel
ก่อนใช้งาน FileChannel จะต้องเปิดก่อน อย่างไรก็ตาม เราไม่สามารถเปิด FileChannel ได้โดยตรง เราจำเป็นต้องได้รับอินสแตนซ์ FileChannel โดยใช้ InputStream, OutputStream หรือ RandomAccessFile นี่คือตัวอย่างการเปิด FileChannel ผ่าน RandomAccessFile:
คัดลอกรหัสรหัสดังต่อไปนี้:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
อ่านข้อมูลจาก FileChannel
เรียกหนึ่งในหลายวิธี read() เพื่ออ่านข้อมูลจาก FileChannel ชอบ:
คัดลอกรหัสรหัสดังต่อไปนี้:
ByteBuffer buf = ByteBuffer.allocate (48);
int bytesRead = inChannel.read(buf);
ขั้นแรกให้จัดสรรบัฟเฟอร์ ข้อมูลที่อ่านจาก FileChannel จะถูกอ่านลงใน Buffer
จากนั้น เรียกเมธอด FileChannel.read() วิธีนี้จะอ่านข้อมูลจาก FileChannel ลงใน Buffer ค่า int ที่ส่งคืนโดยเมธอด read() ระบุจำนวนไบต์ที่ถูกอ่านลงในบัฟเฟอร์ หากส่งคืน -1 แสดงว่าถึงจุดสิ้นสุดของไฟล์แล้ว
เขียนข้อมูลไปที่ FileChannel
ใช้เมธอด FileChannel.write() เพื่อเขียนข้อมูลไปยัง FileChannel พารามิเตอร์ของเมธอดนี้คือบัฟเฟอร์ ชอบ:
คัดลอกรหัสรหัสดังต่อไปนี้:
String newData = "สตริงใหม่ที่จะเขียนลงไฟล์..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate (48);
buf.ชัดเจน();
buf.put(newData.getBytes());
buf.พลิก();
ในขณะที่ (buf.hasRemaining ()) {
ช่อง.write(buf);
-
โปรดทราบว่า FileChannel.write() ถูกเรียกในขณะที่วนซ้ำ เนื่องจากไม่มีการรับประกันว่าเมธอด write() สามารถเขียนไปยัง FileChannel ได้ครั้งละกี่ไบต์ จึงจำเป็นต้องเรียกเมธอด write() ซ้ำๆ กันจนกว่าจะไม่มีไบต์ใน Buffer ที่ยังไม่ได้เขียนลงในแชนเนล
ปิดช่องไฟล์
FileChannel จะต้องปิดเมื่อคุณดำเนินการเสร็จแล้ว ชอบ:
คัดลอกรหัสรหัสดังต่อไปนี้:
ช่อง.ปิด();
วิธีตำแหน่ง FileChannel
บางครั้งอาจจำเป็นต้องอ่าน/เขียนข้อมูลในตำแหน่งเฉพาะใน FileChannel คุณสามารถรับตำแหน่งปัจจุบันของ FileChannel ได้โดยการเรียกเมธอดตำแหน่ง ()
คุณยังสามารถกำหนดตำแหน่งปัจจุบันของ FileChannel ได้โดยการเรียกเมธอดตำแหน่ง (pos แบบยาว)
นี่คือสองตัวอย่าง:
คัดลอกรหัสรหัสดังต่อไปนี้:
pos ยาว = channel.position();
ช่องตำแหน่ง (pos +123);
หากคุณตั้งค่าตำแหน่งหลังสิ้นสุดไฟล์แล้วลองอ่านข้อมูลจากช่องสัญญาณไฟล์ วิธีการอ่านจะส่งกลับ -1 - จุดสิ้นสุดของแฟล็กไฟล์
หากคุณกำหนดตำแหน่งหลังสิ้นสุดไฟล์แล้วเขียนข้อมูลลงในช่อง ไฟล์จะถูกขยายไปยังตำแหน่งปัจจุบันและข้อมูลจะถูกเขียน สิ่งนี้สามารถนำไปสู่ "ช่องโหว่ของไฟล์" ซึ่งเป็นช่องว่างระหว่างข้อมูลที่เขียนในไฟล์ฟิสิคัลบนดิสก์
วิธีขนาด FileChannel
วิธีการ size() ของอินสแตนซ์ FileChannel จะส่งคืนขนาดของไฟล์ที่เกี่ยวข้องกับอินสแตนซ์ ชอบ:
คัดลอกรหัสรหัสดังต่อไปนี้:
ขนาดไฟล์ยาว = channel.size();
วิธีการตัดทอนของ FileChannel
คุณสามารถใช้เมธอด FileChannel.truncate() เพื่อสกัดกั้นไฟล์ เมื่อสกัดกั้นไฟล์ ส่วนที่ตามความยาวของไฟล์ที่ระบุจะถูกลบออก ชอบ:
คัดลอกรหัสรหัสดังต่อไปนี้:
ช่อง.ตัด(1024);
ตัวอย่างนี้สกัดกั้น 1,024 ไบต์แรกของไฟล์
วิธีการบังคับของ FileChannel
เมธอด FileChannel.force() บังคับข้อมูลในช่องสัญญาณที่ยังไม่ได้เขียนลงดิสก์ เพื่อเหตุผลด้านประสิทธิภาพ ระบบปฏิบัติการจะแคชข้อมูลไว้ในหน่วยความจำ ดังนั้นจึงไม่มีการรับประกันว่าข้อมูลที่เขียนไปยัง FileChannel จะถูกเขียนลงดิสก์ทันที เพื่อให้แน่ใจ จึงจำเป็นต้องเรียกใช้เมธอด force()
เมธอด force() มีพารามิเตอร์บูลีนที่ระบุว่าจะเขียนข้อมูลเมตาของไฟล์ (ข้อมูลสิทธิ์ ฯลฯ) ลงดิสก์พร้อมกันหรือไม่
ตัวอย่างต่อไปนี้บังคับให้ทั้งข้อมูลไฟล์และข้อมูลเมตาลงดิสก์:
คัดลอกรหัสรหัสดังต่อไปนี้:
ช่องบังคับ(จริง);
ช่องซ็อกเก็ต
(ลิงก์ไปยังข้อความต้นฉบับของหัวข้อนี้ ผู้แต่ง: Jakob Jenkov ผู้แปล: Zheng Yuting ผู้พิสูจน์อักษร: Ding Yi)
SocketChannel ใน Java NIO เป็นช่องทางที่เชื่อมต่อกับซ็อกเก็ตเครือข่าย TCP SocketChannel สามารถสร้างได้ 2 วิธีดังต่อไปนี้:
เปิด SocketChannel และเชื่อมต่อกับเซิร์ฟเวอร์บนอินเทอร์เน็ต
เมื่อมีการเชื่อมต่อใหม่มาถึง ServerSocketChannel จะมีการสร้าง SocketChannel
เปิด Socket Channel
ต่อไปนี้เป็นวิธีเปิด SocketChannel:
คัดลอกรหัสรหัสดังต่อไปนี้:
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect (InetSocketAddress ใหม่ ("http://jenkov.com", 80));
ปิด SocketChannel
เมื่อคุณใช้ SocketChannel เสร็จแล้ว ให้เรียก SocketChannel.close() เพื่อปิด SocketChannel:
คัดลอกรหัสรหัสดังต่อไปนี้:
socketChannel.close();
อ่านข้อมูลจาก SocketChannel
หากต้องการอ่านข้อมูลจาก SocketChannel ให้เรียกหนึ่งในเมธอด read() นี่คือตัวอย่าง:
คัดลอกรหัสรหัสดังต่อไปนี้:
ByteBuffer buf = ByteBuffer.allocate (48);
int bytesRead = socketChannel.read(buf);
ขั้นแรกให้จัดสรรบัฟเฟอร์ ข้อมูลที่อ่านจาก SocketChannel จะถูกวางไว้ใน Buffer นี้
จากนั้นให้เรียก SocketChannel.read() วิธีนี้จะอ่านข้อมูลจาก SocketChannel ลงใน Buffer ค่า int ที่ส่งคืนโดยเมธอด read() ระบุจำนวนไบต์ที่ถูกอ่านลงในบัฟเฟอร์ หากส่งคืน -1 หมายความว่ามีการอ่านจุดสิ้นสุดของสตรีมแล้ว (การเชื่อมต่อถูกปิด)
เขียนถึง SocketChannel
การเขียนข้อมูลไปยัง SocketChannel ใช้เมธอด SocketChannel.write() ซึ่งรับบัฟเฟอร์เป็นพารามิเตอร์ ตัวอย่างมีดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
String newData = "สตริงใหม่ที่จะเขียนลงไฟล์..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate (48);
buf.ชัดเจน();
buf.put(newData.getBytes());
buf.พลิก();
ในขณะที่ (buf.hasRemaining ()) {
ช่อง.write(buf);
-
โปรดทราบว่าเมธอด SocketChannel.write() จะถูกเรียกใช้ในขณะที่วนซ้ำ เมธอด Write() ไม่สามารถรับประกันได้ว่าสามารถเขียนลงใน SocketChannel ได้กี่ไบต์ ดังนั้นเราจึงเรียก write() ซ้ำ ๆ กันจนกว่า Buffer จะไม่เหลือไบต์ให้เขียน
โหมดไม่ปิดกั้น
คุณสามารถตั้งค่า SocketChannel เป็นโหมดที่ไม่บล็อกได้ หลังจากตั้งค่าแล้ว คุณสามารถเรียกเชื่อมต่อ() อ่าน() และเขียน() ในโหมดอะซิงโครนัส
เชื่อมต่อ()
หาก SocketChannel อยู่ในโหมดที่ไม่ปิดกั้นและเรียกการเชื่อมต่อ () ในขณะนี้ วิธีการอาจส่งคืนก่อนที่จะสร้างการเชื่อมต่อ หากต้องการตรวจสอบว่ามีการสร้างการเชื่อมต่อแล้วหรือไม่ คุณสามารถเรียกใช้เมธอด finishConnect() ได้ แบบนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
socketChannel.configureBlocking (เท็จ);
socketChannel.connect (InetSocketAddress ใหม่ ("http://jenkov.com", 80));
ในขณะที่(! socketChannel.finishConnect() ){
//รอหรือทำอย่างอื่น...
-
เขียน()
ในโหมดไม่บล็อก เมธอด write() อาจกลับมาก่อนที่จะเขียนอะไรก็ตาม ดังนั้นจะต้องเรียก write() ในลูป เคยมีตัวอย่างมาก่อน ดังนั้นฉันจะไม่ลงรายละเอียดที่นี่
อ่าน()
ในโหมดไม่บล็อก เมธอด read() อาจกลับมาก่อนที่จะอ่านข้อมูลใดๆ ดังนั้นคุณต้องใส่ใจกับค่าส่งคืน int ซึ่งจะบอกคุณว่ามีการอ่านกี่ไบต์
โหมดไม่ปิดกั้นและตัวเลือก
โหมดไม่บล็อกทำงานได้ดีกว่ากับตัวเลือก โดยการลงทะเบียน SocketChannels หนึ่งรายการขึ้นไปด้วย Selector คุณสามารถถามตัวเลือกว่าช่องใดพร้อมสำหรับการอ่าน เขียน ฯลฯ การรวมกันของ Selector และ SocketChannel จะมีการหารือในรายละเอียดในภายหลัง
ช่อง ServerSocket
(ลิงก์ไปยังข้อความต้นฉบับของหัวข้อนี้ ผู้แต่ง: Jakob Jenkov ผู้แปล: Zheng Yuting ผู้พิสูจน์อักษร: Ding Yi)
ServerSocketChannel ใน Java NIO เป็นช่องทางที่สามารถฟังการเชื่อมต่อ TCP ขาเข้าใหม่ได้ เช่นเดียวกับ ServerSocket ใน IO มาตรฐาน คลาส ServerSocketChannel อยู่ในแพ็คเกจ java.nio.channels
นี่คือตัวอย่าง:
คัดลอกรหัสรหัสดังต่อไปนี้:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind (InetSocketAddress ใหม่ (9999));
ในขณะที่ (จริง) {
SocketChannel socketChannel =
serverSocketChannel.accept ();
//ทำอะไรบางอย่างกับ socketChannel...
-
เปิด ServerSocketChannel
เปิด ServerSocketChannel โดยการเรียกเมธอด ServerSocketChannel.open() ตัวอย่างเช่น:
คัดลอกรหัสรหัสดังต่อไปนี้:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ปิด ServerSocketChannel
ปิด ServerSocketChannel โดยการเรียกเมธอด ServerSocketChannel.close() ตัวอย่างเช่น:
คัดลอกรหัสรหัสดังต่อไปนี้:
serverSocketChannel.close();
รับฟังการเชื่อมต่อใหม่ๆ ที่เข้ามา
ฟังการเชื่อมต่อขาเข้าใหม่ผ่านเมธอด ServerSocketChannel.accept() เมื่อเมธอด Accept() ส่งคืน ก็จะส่งคืน SocketChannel ที่มีการเชื่อมต่อขาเข้าใหม่ ดังนั้นเมธอด Accept() จะบล็อกจนกว่าการเชื่อมต่อใหม่จะมาถึง
โดยปกติแทนที่จะฟังเพียงการเชื่อมต่อเดียว เมธอด Accept() จะถูกเรียกใน while loop ดังตัวอย่างต่อไปนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
ในขณะที่ (จริง) {
SocketChannel socketChannel =
serverSocketChannel.accept ();
//ทำอะไรบางอย่างกับ socketChannel...
-
แน่นอน คุณยังสามารถใช้เกณฑ์การออกอื่นๆ นอกเหนือจาก true ใน while loop ได้
โหมดไม่ปิดกั้น
ServerSocketChannel สามารถตั้งค่าเป็นโหมดที่ไม่ปิดกั้นได้ ในโหมดไม่บล็อก เมธอด Accept() จะกลับมาทันที หากไม่มีการเชื่อมต่อเข้ามาใหม่ ค่าที่ส่งคืนจะเป็นโมฆะ ดังนั้น คุณต้องตรวจสอบว่า SocketChannel ที่ส่งคืนนั้นเป็นโมฆะหรือไม่ ชอบ:
คัดลอกรหัสรหัสดังต่อไปนี้:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind (InetSocketAddress ใหม่ (9999));
serverSocketChannel.configureBlocking (เท็จ);
ในขณะที่ (จริง) {
SocketChannel socketChannel =
serverSocketChannel.accept ();
ถ้า (socketChannel != null){
//ทำอะไรบางอย่างกับ socketChannel...
-
-
ช่องทางดาต้าแกรม
(ลิงก์ไปยังข้อความต้นฉบับของหัวข้อนี้ ผู้แต่ง: Jakob Jenkov ผู้แปล: Zheng Yuting ผู้พิสูจน์อักษร: Ding Yi)
DatagramChannel ใน Java NIO เป็นช่องทางที่สามารถส่งและรับแพ็คเก็ต UDP เนื่องจาก UDP เป็นโปรโตคอลเครือข่ายแบบไร้การเชื่อมต่อ จึงไม่สามารถอ่านและเขียนได้เหมือนช่องอื่นๆ มันส่งและรับแพ็กเก็ตข้อมูล
OpenDatagramChannel
นี่คือวิธีการเปิด DatagramChannel:
คัดลอกรหัสรหัสดังต่อไปนี้:
ช่อง DatagramChannel = DatagramChannel.open();
channel.socket().bind(ใหม่ InetSocketAddress(9999));
DatagramChannel ที่เปิดโดยตัวอย่างนี้สามารถรับแพ็กเก็ตบนพอร์ต UDP 9999
รับข้อมูล
รับข้อมูลจาก DatagramChannel ผ่านวิธีการรับ () เช่น:
คัดลอกรหัสรหัสดังต่อไปนี้:
ByteBuffer buf = ByteBuffer.allocate (48);
buf.ชัดเจน();
ช่อง.รับ(buf);
วิธีการรับ () จะคัดลอกเนื้อหาแพ็คเก็ตข้อมูลที่ได้รับไปยังบัฟเฟอร์ที่ระบุ หากบัฟเฟอร์ไม่สามารถรองรับข้อมูลที่ได้รับ ข้อมูลส่วนเกินจะถูกละทิ้ง
ส่งข้อมูล
ส่งข้อมูลจาก DatagramChannel ผ่านเมธอด send() เช่น:
คัดลอกรหัสรหัสดังต่อไปนี้:
String newData = "สตริงใหม่ที่จะเขียนลงไฟล์..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate (48);
buf.ชัดเจน();
buf.put(newData.getBytes());
buf.พลิก();
int bytesSent = channel.send (buf, InetSocketAddress ใหม่ ("jenkov.com", 80));
ตัวอย่างนี้ส่งสตริงอักขระไปยังพอร์ต UDP 80 ของเซิร์ฟเวอร์ "jenkov.com" เนื่องจากเซิร์ฟเวอร์ไม่ได้ตรวจสอบพอร์ตนี้ จึงไม่มีอะไรเกิดขึ้น นอกจากนี้ยังจะไม่แจ้งให้คุณทราบว่าได้รับแพ็กเก็ตขาออกหรือไม่ เนื่องจาก UDP ไม่มีการรับประกันใด ๆ ในแง่ของการส่งข้อมูล
เชื่อมต่อกับที่อยู่เฉพาะ
DatagramChannel สามารถ "เชื่อมต่อ" ไปยังที่อยู่เฉพาะในเครือข่ายได้ เนื่องจาก UDP ไม่มีการเชื่อมต่อ การเชื่อมต่อกับที่อยู่เฉพาะจึงไม่สร้างการเชื่อมต่อจริงเหมือนกับช่อง TCP แต่ DatagramChannel จะถูกล็อคเพื่อให้สามารถส่งและรับข้อมูลจากที่อยู่ที่ระบุเท่านั้น
นี่คือตัวอย่าง:
คัดลอกรหัสรหัสดังต่อไปนี้:
channel.connect(ใหม่ InetSocketAddress("jenkov.com", 80));
เมื่อเชื่อมต่อแล้ว คุณยังสามารถใช้เมธอด read() และ write() ได้เหมือนกับที่คุณใช้กับแชนเนลแบบเดิม ไม่มีการรับประกันเกี่ยวกับการถ่ายโอนข้อมูล นี่คือตัวอย่างบางส่วน:
คัดลอกรหัสรหัสดังต่อไปนี้:
int bytesRead = channel.read(buf);
int bytesWritten = channel.write(แต่);
ท่อ
(ลิงก์ไปยังข้อความต้นฉบับของหัวข้อนี้ ผู้แต่ง: Jakob Jenkov ผู้แปล: Huang Zhong ผู้พิสูจน์อักษร: Ding Yi)
ไปป์ Java NIO คือการเชื่อมต่อข้อมูลทางเดียวระหว่าง 2 เธรด ไปป์มีช่องต้นทางและช่องอ่างล้างจาน ข้อมูลจะถูกเขียนลงในช่อง sink และอ่านจากช่องต้นทาง
นี่คือภาพประกอบของหลักการของ Pipe:
สร้างไปป์ไลน์
เปิดไปป์ผ่านวิธี Pipe.open() ตัวอย่างเช่น:
คัดลอกรหัสรหัสดังต่อไปนี้:
ท่อ ท่อ = Pipe.open();
เขียนข้อมูลลงในไปป์
หากต้องการเขียนข้อมูลลงในไปป์ คุณต้องเข้าถึงช่องซิงก์ แบบนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
Pipe.SinkChannel sinkChannel = pipe.sink();
เขียนข้อมูลไปยัง SinkChannel โดยการเรียกเมธอด write() ของ SinkChannel เช่นนี้
คัดลอกรหัสรหัสดังต่อไปนี้:
String newData = "สตริงใหม่ที่จะเขียนลงไฟล์..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate (48);
buf.ชัดเจน();
buf.put(newData.getBytes());
buf.พลิก();
ในขณะที่ (buf.hasRemaining ()) {
<b>sinkChannel.write(buf);</b>
-
[รหัส]
อ่านข้อมูลจากไปป์
หากต้องการอ่านข้อมูลจากไปป์ คุณต้องเข้าถึงช่องทางต้นทาง เช่นนี้
[รหัส]
Pipe.SourceChannel sourceChannel = ไปป์.ซอร์ส();
เรียกเมธอด read() ของช่องสัญญาณต้นทางเพื่ออ่านข้อมูล เช่นนี้
คัดลอกรหัสรหัสดังต่อไปนี้:
ByteBuffer buf = ByteBuffer.allocate (48);
int bytesRead = inChannel.read(buf);
ค่า int ที่ส่งคืนโดยเมธอด read() จะบอกเราว่ามีการอ่านไบต์จำนวนเท่าใดในบัฟเฟอร์