لا تشعر IO بالارتباط كثيرًا بـ Multi-Thoreading ، لكن NIO غيرت الطريقة التي يتم بها استخدام المواضيع على مستوى التطبيق وحل بعض الصعوبات العملية. AIO غير متزامن IO والسلسلة السابقة مرتبطة أيضًا. هنا ، للتعلم والتسجيل ، كتبت أيضًا مقالًا لتقديم NIO و AIO.
1. ما هو NIO
NIO هو اختصار I/O جديد ، والذي يعكس طريقة I/O القائمة على الدفق القديم. انطلاقًا من الاسم ، فإنه يمثل مجموعة جديدة من معايير Java I/O. تم دمجه في JDK في Java 1.4 وله الميزات التالية:
يجب أن تمر جميع عمليات القراءة والكتابة من القناة من خلال المخزن المؤقت ، والقناة هي تجريد IO ، والطرف الآخر من القناة هو الملف المعالج.
2. العازلة
تنفيذ المخزن المؤقت في جافا. أنواع البيانات الأساسية لها مخازن المؤقتة المقابلة لها
أمثلة بسيطة لاستخدام المخزن المؤقت:
اختبار الحزمة استيراد java.io.file ؛ استيراد java.io.fileinputStream ؛ استيراد java.nio.bytebuffer ؛ استيراد java.nio.channels.filechannel ؛ اختبار الفئة العامة {public static void main (string [] args) يرمي الاستثناء {fileInputStream fin = جديد fileInputStream (ملف جديد ("d: //temp_buffer.tmp") ؛ filechannel fc = fin.getChannel () ؛ bytebuffer bytebuffer = bytebuffer.allocate (1024) ؛ fc.read (bytebuffer) ؛ fc.close () ؛ bytebuffer.flip () ؛ // قراءة وكتابة التحويل}}}يتم تلخيص الخطوات المراد استخدامها:
1. احصل على قناة
2. تقدم بطلب للحصول على مخزن مؤقت
3. إنشاء علاقة قراءة/كتابة بين القناة والمخزن المؤقت
4
المثال التالي هو استخدام NIO لنسخ الملفات:
public static void niocopyfile (سلسلة المورد ، الوجهة السلسلة) يلقي ioException {fileInputStream fis = new FileInputStream (Resource) ؛ fileOutputStream fos = جديد fileOutputStream (الوجهة) ؛ filechannel readchannel = fis.getChannel () ؛ // قراءة ملفات الملف FileChannel WriteChannel = fos.getChannel () ؛ // اكتب قناة الملف bytebuffer buffer = bytebuffer.allocate (1024) ؛ // اقرأ في ذاكرة التخزين المؤقت للبيانات بينما (صحيح) {buffer.clear () ؛ int len = ReadChannel.Read (Buffer) ؛ // اقرأ في البيانات if (len == -1) {break ؛ // اقرأ في} buffer.flip () ؛ Writechannel.write (Buffer) ؛ // اكتب إلى الملف} readchannel.close () ؛ Writechannel.close () ؛ }هناك 3 معلمات مهمة في المخزن المؤقت: الموضع والقدرة والحد
هنا نحتاج إلى التمييز بين السعة والحد العلوي. على سبيل المثال ، إذا كان لدى المخزن المؤقت 10 كيلو بايت ، فإن 10 كيلو بايت هو السعة. إذا قرأت ملف 5 كيلو بايت في المخزن المؤقت ، فإن الحد الأعلى هو 5 كيلو بايت.
فيما يلي مثال لفهم هذه المعلمات الثلاثة المهمة:
static static void main (string [] args) يلقي الاستثناء {bytebuffer b = bytebuffer.allocate (15) ؛ // 15-byte buffer system.out.println ("limit =" + b.limit () + "caption =" + b.capacity () + "position =" + b.position ()) ؛ لـ (int i = 0 ؛ i <10 ؛ i ++) {// حفظ 10 بايت من البيانات b.put ((byte) i) ؛ } system.out.println ("limit =" + b.limit () + "cappy =" + b.capacity () + "position =" + b.position ()) ؛ B.flip () ؛ // reset position system.out.println ("limit =" + b.limit () + "cute =" + b.capacity () + "position =" + b.position ()) ؛ لـ (int i = 0 ؛ i <5 ؛ i ++) {system.out.print (b.get ()) ؛ } system.out.println () ؛ System.out.println ("limit =" + b.limit () + "cappary =" + b.Capacity () + "position =" + b.position ()) ؛ B.flip () ؛ System.out.println ("limit =" + b.limit () + "cappary =" + b.Capacity () + "position =" + b.position ()) ؛ }يتم عرض العملية برمتها في الشكل:
في هذا الوقت ، يكون الموقف من 0 إلى 10 ، ويظل السعة والحدود دون تغيير.
هذه العملية تعيد ضبط الموقف. عادة ، عند تحويل المخزن المؤقت من وضع الكتابة إلى وضع القراءة ، تحتاج إلى تنفيذ هذه الطريقة. لا تقوم عملية Flip () بإعادة تعيين الموضع الحالي إلى 0 فحسب ، بل تحدد أيضًا الحد لموضع الموضع الحالي.
معنى الحد هو تحديد البيانات ذات مغزى. بمعنى آخر ، فإن البيانات من الموضع إلى الحد هي بيانات ذات معنى لأنها البيانات من العملية الأخيرة. لذلك ، غالبًا ما تعني العمليات الوجه القراءة والكتابة.
نفس المعنى على النحو الوارد أعلاه.
معظم الطرق في المخزن المؤقت تغير هذه المعلمات الثلاثة لتحقيق وظائف معينة:
العازلة النهائية العامة العازلة ()
تعيين الموضع صفر وعلامة واضحة
المخزن المؤقت النهائي العام واضح ()
تعيين الموضع صفر ، وضبط الحد على حجم السعة ومسح علامة العلم
العازلة النهائية العازلة الوجه ()
الحد الأول للموضع الذي يكون فيه الموضع ، ثم تعيين الموضع على الصفر ، وقم بمسح علامة بت العلم ، وعادة ما يتم استخدامه أثناء تحويل القراءة والكتابة
تم تعيين ملف إلى الذاكرة
الباطل الثابت العام (سلسلة [] args) يلقي الاستثناء {randomaccessfile raf = new randomaccessfile ("c: //mapfile.txt" ، "rw") ؛ filechannel fc = raf.getChannel () ؛ // خريطة الملف في الذاكرة medbytebuffer mbb = fc.map (filechannel.mapmode.read_write ، 0 ، raf.length ()) ؛ بينما (mbb.hasremaining ()) {system.out.print ((char) mbb.get ()) ؛ } mbb.put (0 ، (byte) 98) ؛ // تعديل الملف raf.close () ؛ }تعديل medbytebuffer يعادل تعديل الملف نفسه ، وبالتالي فإن سرعة العملية سريعة للغاية.
3. القناة
الهيكل العام لخادم الشبكة متعدد الخيوط:
خادم بسيط متعدد الخيوط:
static static void main (string [] args) يلقي الاستثناء {serversocket echoServer = null ؛ socket clientsocket = null ؛ حاول {echoserver = new ServersOcket (8000) ؛ } catch (ioException e) {system.out.println (e) ؛ } بينما (صحيح) {try {clientsocket = echoServer.accept () ؛ System.out.println (clientsocket.getRemotesOcketAddress () + "connect!") ؛ tp.execute (new Handlemsg (clientsocke)) ؛ } catch (ioException e) {system.out.println (e) ؛ }}}تتمثل الوظيفة في كتابة البيانات إلى العميل عندما يقرأ الخادم.
هنا TP هو تجمع مؤشرات ترابط ، و handlemsg هو الفئة التي تتعامل مع الرسائل.
تنفذ Handlemsg الفئة الثابتة {حذف جزء من المعلومات public void run () {try {is = new BufferEdReader (new inputStreamReader (clientsocket.getInputStream ())) ؛ OS = new printWriter (clientsocket.getOutputStream () ، true) ؛ // اقرأ البيانات المرسلة من قبل العميل من inpurnline inputStream inputLine = null ؛ long b = system.currentTimeMillis () ؛ بينما ((inputLine = iS.ReadLine ())! = null) {os.println (inputLine) ؛ } طويل e = النظام. CurrentTimeMillis () ؛ نظام. out.println ("Endress:"+(E - B)+"MS") ؛ } catch (ioException e) {E.PrintStackTrace () ؛ } أخيرًا {Close Resource}}}عميل:
static static void main (string [] args) يلقي الاستثناء {socket client = null ؛ printwriter كاتب = فارغ ؛ BufferedReader Reader = NULL ؛ حاول {client = new Socket () ؛ client.connect (new inetsocketaddress ("LocalHost" ، 8000)) ؛ الكاتب = new printWriter (client.getOutputStream () ، true) ؛ الكاتب. println ("مرحبا!") ؛ الكاتب. flush () ؛ reader = جديد bufferedReader (new inputStreamReader (client.getInputStream ())) ؛ System.out.println ("من الخادم:" + reader.ReadLine ()) ؛ } catch (استثناء e) {} أخيرًا {// حذف إغلاق الموارد}}برمجة الشبكة أعلاه أساسية للغاية ، وستكون هناك بعض المشكلات باستخدام هذه الطريقة:
استخدم موضوع واحد لكل عميل. إذا واجه العميل استثناء مثل التأخير ، فقد يتم احتلال الخيط لفترة طويلة. لأن إعداد البيانات والقراءة في هذا الموضوع. في هذا الوقت ، إذا كان هناك العديد من العملاء ، فقد يستهلك الكثير من موارد النظام.
حل:
استخدم NIO غير المحظور (اقرأ البيانات دون انتظار ، فإن البيانات جاهزة قبل العمل)
من أجل عكس كفاءة استخدام NIO.
هنا نقوم أولاً بمحاكاة عميل غير فعال لمحاكاة التأخير بسبب الشبكة:
static eventorservice tp = Executors.NewCacheDthReadPool () ؛ استاتيكي خاص نهائي int sleep_time = 1000*1000*1000 ؛ تنفذ echoclient الفئة الثابتة العامة {public void run () {try {client = new Socket () ؛ client.connect (new inetsocketaddress ("LocalHost" ، 8000)) ؛ الكاتب = new printWriter (client.getOutputStream () ، true) ؛ الكاتب. locksupport.parknanos (sleep_time) ؛ الكاتب. locksupport.parknanos (sleep_time) ؛ الكاتب. locksupport.parknanos (sleep_time) ؛ الكاتب. locksupport.parknanos (sleep_time) ؛ الكاتب. locksupport.parknanos (sleep_time) ؛ الكاتب. locksupport.parknanos (sleep_time) ؛ الكاتب. println () ؛ الكاتب. flush () ؛ } catch (استثناء e) {}}}الإخراج من جانب الخادم:
إنفاق: 6000ms
إنفاق: 6000ms
إنفاق: 6000ms
إنفاق: 6001 مللي ثانية
إنفاق: 6002 مللي ثانية
إنفاق: 6002 مللي ثانية
إنفاق: 6002 مللي ثانية
إنفاق: 6002 مللي ثانية
إنفاق: 6003 مللي ثانية
إنفاق: 6003 مللي ثانية
لأن
بينما ((inpectline = IS.ReadLine ())! = NULL)
تم حظره ، لذلك يتم قضاء الوقت في الانتظار.
ماذا سيحدث إذا تم استخدام NIO للتعامل مع هذه المشكلة؟
واحدة من الميزات الكبيرة لـ NIO هي: أبلغني إذا كانت البيانات جاهزة
تشبه القناة بعض التدفقات ، ويمكن للقناة أن تتوافق مع الملفات أو مآخذ الشبكة.
المحدد هو محدد يمكنه اختيار قناة والقيام بشيء ما.
يمكن أن يتوافق الخيط مع المحدد ، ويمكن للمحدد استطلاع قنوات متعددة ، وكل قناة لها مأخذ توصيل.
بالمقارنة مع الخيط أعلاه المقابل للمقبس ، بعد استخدام NIO ، يمكن لخيط الاستطلاع مآخذ متعددة.
عندما يقوم المحدد بالتحديد () ، سيتحقق مما إذا كان أي عميل قد أعد البيانات. عندما لا تكون البيانات جاهزة ، سيتم تحديد (). عادة ما يقال أن NIO غير محظور ، ولكن إذا لم تكن البيانات جاهزة ، فستظل هناك حظر.
عندما تكون البيانات جاهزة ، بعد استدعاء SELECT () ، سيتم إرجاع مفتاح التحديد. يشير مفتاح التحديد إلى أنه تم إعداد بيانات قناة على أحد المحددات.
سيتم تحديد هذه القناة فقط عندما تكون البيانات جاهزة.
وبهذه الطريقة ، يقوم NIO بتنفيذ موضوع لمراقبة عملاء متعددين.
لن يؤثر العميل الذي يتمتع بتأخير الشبكة فقط على مؤشرات الترابط تحت NIO ، لأنه عندما يتم تأخير شبكة المقبس ، فإن البيانات غير جاهزة ، ولن يحدد المحدد ذلك ، ولكنه سيختار العملاء الآخرين المعدين.
الفرق بين SelectNow () و SELECT () هو أن SELECTNOW () لا يحظر. عندما لا يقوم أي عميل بإعداد البيانات ، لن يحظر SelectNow () وسيعود 0. عندما يقوم العميل بإعداد البيانات ، يقوم SelectNow () بإرجاع عدد العملاء الجاهز.
الرمز الرئيسي:
اختبار الحزمة استيراد java.net.inetaddress ؛ استيراد java.net.inetsocketaddress ؛ استيراد java.net.socket ؛ استيراد java.nio.channels.selectionkeke ؛ java.nio.channels.socketchannel ؛ استيراد java.nio.channels.spi.abstractseSelector java.util.concurrent.executorservice ؛ import java.util.concurrent.executors ؛ الفئة العامة multiThReadNioEchoserver {خريطة ثابتة عامة <socket ، long> geym_time_stat = new hashmap <socket ، long> () ؛ class echoclient {private linkedList <Bytebuffer> outq ؛ echoclient () {outq = new LinkedList <Bytebuffer> () ؛ } LinkedList Public <Bytebuffer> getOutputQueue () {return outq ؛ } public void enqueue (bytebuffer bb) {outq.addfirst (bb) ؛ }} class handlemsg تنفذ runnable {selectekey sk ؛ Bytebuffer BB ؛ HandleMsg العامة (Selectekey SK ، Bytebuffer BB) {super () ؛ this.sk = sk ؛ this.bb = bb ؛ } Override public void run () {// todo method method tuto clud echoclient echoclient = (echoclient) sk.attachment () ؛ echoclient.enqueue (bb) ؛ sk.Interestops (selectekey.op_read | selectekey.op_write) ؛ celector.wakeup () ؛ }} محدد المحدد الخاص ؛ evecorsorservice tp = evelivors.newcachedthreadpool () ؛ private void starterver () يلقي استثناء {celector = celectorprovider.provider (). openSelector () ؛ serversocketchannel ssc = serversocketchannel.open () ؛ ssc.configureBlocking (false) ؛ inetsocketaddress ISA = new inetsocketaddress (8000) ؛ ssc.socket (). bind (ISA) ؛ . لـ (؛؛) {selector.select () ؛ Set ashykeys = selector.selectedKeys () ؛ iterator i = assykeys.iterator () ؛ طويل e = 0 ؛ بينما (i.hasNext ()) {selectekey sk = (selectekey) i.next () ؛ I.Remove () ؛ if (sk.isacrocpion ()) {doaccept (sk) ؛ } آخر إذا (sk.isvalid () && sk.isreadable ()) {if (! geym_time_stat.containskey (((socketchannel) skannel ()). socket ()))) } doread (sk) ؛ } if if (sk.isvalid () && sk.iswitable ()) {dowrite (sk) ؛ e = system.currentTimeMillis () ؛ long b = geym_time_stat.remove (((socketchannel) sk .Channel ()). socket ()) ؛ System.out.println ("Sending:" + (E - B) + "MS") ؛ }}}} private void dowrite (selectekey sk) {// todo method method method socketchannel channel = (socketchannel) sk.channel () ؛ echoclient echoclient = (echoclient) sk.attachment () ؛ LinkedList <Bytebuffer> outq = echoclient.getOutputQueue () ؛ bytebuffer bb = outq.getlast () ؛ حاول {int len = channel.write (bb) ؛ if (len == -1) {disconnect (sk) ؛ يعود؛ } if (bb.remaining () == 0) {outq.Removelast () ؛ }} catch (استثناء e) {// todo: التعامل مع الاستثناء disconnect (sk) ؛ } if (outq.size () == 0) {sk.InterestOps (selecteKey.op_read) ؛ }} private void doread (selectekey sk) {// todo method method method tuto clockethannel channel = (socketchannel) sk.channel () ؛ bytebuffer bb = bytebuffer.allocate (8192) ؛ int len ؛ حاول {len = channel.read (bb) ؛ if (len <0) {disconnect (sk) ؛ يعود؛ }} catch (استثناء e) {// todo: التعامل مع الاستثناء disconnect (sk) ؛ يعود؛ } bb.flip () ؛ tp.execute (new Handlemsg (SK ، BB)) ؛ } تفكيك باطل خاص (Selectekey SK) {// todo method method method clud // repling exp exp extracting extrocketchannel server coveroCkAnnel server = (costerocketchannel) sk.channel () ؛ Socketchannel ClientChannel ؛ حاول {clientChannel = server.accept () ؛ clientChannel.ConfigureBlocking (false) ؛ clientKey SelecteKey = clientChannel.register (selector ، selectekey.op_read) ؛ echoclient echoclinet = echoclient جديد () ؛ clientKey.attach (echoclinet) ؛ inetaddress clientAddress = clientChannel.socket (). getInetAddress () ؛ System.out.println ("اتصال مقبول من" + clientAddress.gethostaddress ()) ؛ } catch (استثناء e) {// todo: مقبض الاستثناء}} الفراغ الثابت العام (سلسلة [] args) {// todo method method multithreadnioechoserver echoserver = new MultiThReadNioEchoserver () ؛ حاول {echoserver.startServer () ؛ } catch (استثناء e) {// todo: مقبض الاستثناء}}}}الرمز هو للرجوع إليه فقط ، وميزته الرئيسية هي أنك مهتم بأحداث مختلفة للقيام بأشياء مختلفة.
عند استخدام العميل المتأخر الذي تم محاكاته في وقت سابق ، يكون استهلاك الوقت هذه المرة بين 2 مللي ثانية و 11 مللي ثانية. تحسين الأداء واضح.
تلخيص:
1. بعد إعداد NIO للبيانات ، سوف تسليمها إلى تطبيق المعالجة. لا تزال عملية قراءة/كتابة البيانات مكتملة في مؤشر ترابط التطبيق ، وستقوم فقط بتجريد وقت الانتظار إلى سلسلة رسائل منفصلة.
2. حفظ وقت إعداد البيانات (لأنه يمكن إعادة استخدام المحدد)
5. aio
ميزات AIO:
1. أبلغني بعد القراءة
2. لن يتم تسريع IO ، ولكن سيتم إخطاره بعد قراءته
3. استخدم وظائف رد الاتصال لأداء معالجة الأعمال
الرمز المرتبط AIO:
غير متزامن
Server = AsynchronousServersOcketchAnnel.open (). bind (new inetsocketaddress (port)) ؛
استخدم طريقة قبول على الخادم
الملخص العام <a> void قبول (مرفق ، إكمال <sonchronoussocketchannel ،؟ super a> معالج) ؛
الانتهاء هي واجهة رد الاتصال. عندما يكون هناك عميل يقبل ، فإنه يفعل ما هو موجود في المعالج.
نموذج الرمز:
server.accept (null ، completehandler new <SynchronousSocketchAnly ، object> () {Final Bytebuffer Buffer = Bytebuffer.Allocate (1024) ؛ public void الانتهاء (AsynchronousSockArnel. {buffer.clear () ؛ server.accept (هذا) ؛هنا نستخدم المستقبل لتحقيق عائد فوري. للمستقبل ، يرجى الرجوع إلى المقالة السابقة
استنادًا إلى فهم NIO ، بالنظر إلى AIO ، فإن الفرق هو أن AIO ينتظر عملية القراءة والكتابة للاتصال بوظيفة رد الاتصال قبل الانتهاء.
NIO متزامن وغير محظور
AIO غير متزامن وغير محظور
نظرًا لأن عملية قراءة وكتابة NIO لا تزال مكتملة في مؤشر ترابط التطبيق ، فإن NIO غير مناسبة لأولئك الذين لديهم عملية قراءة طويلة وكتابة طويلة.
لا يتم إخطار عملية قراءة AIO والكتابة إلا بعد اكتمالها ، لذا فإن AIO مؤهل لمهام عملية القراءة والكتابة على المدى الطويل.