Java NIO (الإدخال/الإخراج الجديد) - حزمة واجهة برمجة التطبيقات (API) الجديدة للإدخال/الإخراج - تم تقديمها في J2SE 1.4 في عام 2002. الهدف من Java NIO هو تحسين أداء مهام الإدخال/الإخراج المكثفة على نظام Java الأساسي. بعد مرور عشر سنوات، لا يزال العديد من مطوري Java لا يعرفون كيفية الاستفادة الكاملة من NIO، ويعلم عدد أقل من الأشخاص أن واجهة برمجة تطبيقات الإدخال/الإخراج المحدثة (NIO.2) تم تقديمها في Java SE 7. أكبر مساهمة من NIO وNIO.2 في منصة Java هي تحسين أداء المكون الأساسي في تطوير تطبيقات Java: معالجة الإدخال/الإخراج. ومع ذلك، لا تعد أي من الحزمتين مفيدًا جدًا، كما أنها ليست مناسبة لجميع السيناريوهات. إذا تم استخدام Java NIO وNIO.2 بشكل صحيح، فيمكنهما تقليل الوقت المستغرق في بعض عمليات الإدخال/الإخراج الشائعة بشكل كبير. هذه هي القوة الخارقة لـ NIO و NIO.2، وفي هذه المقالة سأعرض لك 5 طرق بسيطة لاستخدامهما.
تغيير الإشعارات (لأن كل حدث يتطلب مستمعًا)
المحددات والإدخال والإدخال غير المتزامن: تحسين تعدد الإرسال من خلال المحددات
قناة - الوعد والواقع
رسم خرائط الذاكرة - يتم استخدام الفولاذ الجيد على الشفرة
ترميز الأحرف والبحث
خلفية NIO
لماذا تعتبر حزمة التحسين الموجودة منذ 10 سنوات بمثابة حزمة إدخال/إخراج جديدة لـ Java؟ والسبب هو أن عمليات الإدخال / الإخراج الأساسية كافية بالنسبة لمعظم مبرمجي Java. في العمل اليومي، لا يحتاج معظم مطوري Java إلى تعلم NIO. ولأخذ خطوة إلى الأمام، فإن NIO هي أكثر من مجرد حزمة لتحسين الأداء. بدلاً من ذلك، فهي عبارة عن مجموعة من الوظائف المختلفة المتعلقة بـ Java I/O. تحقق NIO تحسينًا في الأداء من خلال جعل أداء تطبيقات Java "أقرب إلى الجوهر"، مما يعني أن واجهات برمجة التطبيقات الخاصة بـ NIO وNIO.2 تكشف المدخل لعمليات النظام ذات المستوى المنخفض. سعر NIO هو أنه على الرغم من أنه يوفر إمكانات تحكم أكثر قوة في الإدخال / الإخراج، إلا أنه يتطلب منا أيضًا الاستخدام والتدرب بعناية أكبر من برمجة الإدخال / الإخراج الأساسية. ميزة أخرى لـ NIO هي تركيزها على تعبير التطبيق، وهو ما سنراه في التمارين التالية.
ابدأ في تعلم NIO وNIO.2
هناك العديد من المواد المرجعية لـ NIO - بعض الروابط المختارة في المواد المرجعية. لتعلم NIO وNIO.2، لا غنى عن وثائق Java 2 SDK Standard Edition (SE) ووثائق Java SE 7. لاستخدام الكود الموجود في هذه المقالة، يجب عليك استخدام JDK 7 أو أعلى.
بالنسبة للعديد من المطورين، قد تكون المرة الأولى التي يواجهون فيها NIO عند صيانة التطبيقات: التطبيق الذي يعمل يصبح أبطأ فأبطأ، لذلك يقترح بعض الأشخاص استخدام NIO لتحسين سرعة الاستجابة. يعتبر NIO رائعًا عندما يتعلق الأمر بتحسين أداء التطبيق، لكن النتائج المحددة تعتمد على النظام الأساسي (لاحظ أن NIO يعتمد على النظام الأساسي). إذا كانت هذه هي المرة الأولى التي تستخدم فيها NIO، فأنت بحاجة إلى وزنه بعناية. ستجد أن قدرة NIO على تحسين الأداء لا تعتمد فقط على نظام التشغيل، ولكن أيضًا على JVM الذي تستخدمه، والسياق الافتراضي للمضيف، وخصائص التخزين كبير السعة، وحتى البيانات. ولذلك، فإن عمل قياس الأداء أمر صعب نسبيا. خاصة عندما يكون نظامك لديه بيئة نشر محمولة، فأنت بحاجة إلى إيلاء اهتمام خاص.
بعد فهم المحتوى أعلاه، لا داعي للقلق الآن، فلنختبر الوظائف الخمس المهمة لـ NIO وNIO.2.
1. تغيير الإشعار (لأن كل حدث يتطلب مستمعًا)
أحد الاهتمامات المشتركة بين المطورين المهتمين بـ NIO وNIO.2 هو أداء تطبيقات Java. من خلال تجربتي، يعد المنبه لتغيير الملف في NIO.2 هو الميزة الأكثر إثارة للاهتمام (والتي تم الاستخفاف بها) في واجهة برمجة تطبيقات الإدخال/الإخراج الجديدة.
تتطلب العديد من التطبيقات على مستوى المؤسسة معالجة خاصة في المواقف التالية:
عندما يتم تحميل ملف إلى مجلد FTP
عندما يتم تعديل تعريف في التكوين
عندما يتم تحميل مسودة المستند
عند حدوث أحداث أخرى لنظام الملفات
هذه كلها أمثلة على إشعارات التغيير أو استجابات التغيير. في الإصدارات السابقة من Java (واللغات الأخرى)، كان الاستقصاء هو أفضل طريقة لاكتشاف أحداث التغيير هذه. يعد الاستقصاء نوعًا خاصًا من الحلقات اللانهائية: قم بفحص نظام الملفات أو الكائنات الأخرى، ومقارنتها بالحالة السابقة. إذا لم يكن هناك تغيير، استمر في التحقق بعد فترة زمنية تبلغ حوالي بضع مئات من المللي ثانية أو 10 ثوانٍ. ويستمر هذا في حلقة لا نهاية لها.
يوفر NIO.2 طريقة أفضل لإجراء اكتشاف التغيير. القائمة 1 هي مثال بسيط.
القائمة 1. تغيير آلية الإعلام في NIO.2
انسخ رمز الكود كما يلي:
import java.nio.file.attribute.*;
importjava.io.*;
importjava.util.*;
importjava.nio.file.Path;
importjava.nio.file.Paths;
importjava.nio.file.StandardWatchEventKinds;
importjava.nio.file.WatchEvent;
importjava.nio.file.WatchKey;
importjava.nio.file.WatchService;
importjava.util.List;
publicclassWatcher{
publicstaticvoidmain(String[]args){
Paththis_dir=Paths.get(".");
System.out.println("يتم الآن مشاهدة الدليل الحالي...");
يحاول{
WatchServicewatcher=this_dir.getFileSystem().newWatchService();
this_dir.register(watcher,StandardWatchEventKinds.ENTRY_CREATE);
WatchKeywatckKey=watcher.take();
List<WatchEvent<<64;>>events=watckKey.pollEvents();
ل(WatchEventevent:الأحداث){
System.out.println("شخص ما قام بإنشاء الملف'"+event.context().toString()+"'.");
}
}قبض(استثناء){
System.out.println("خطأ:"+e.toString());
}
}
}
قم بتجميع هذا الرمز وتنفيذه من سطر الأوامر. في نفس الدليل، قم بإنشاء ملف جديد، على سبيل المثال، قم بتشغيل الأمر touchexample أو CopyWatcher.classexample. ستظهر لك رسالة إشعار التغيير التالية:
شخص ما فقط يقوم بإنشاء الملف "example1".
يوضح هذا المثال البسيط كيفية البدء في استخدام وظيفة JavaNIO. وفي الوقت نفسه، يقدم أيضًا فئة NIO.2 Watcher، وهي أكثر مباشرة وأسهل في الاستخدام من نظام الاستقصاء في الإدخال/الإخراج الأصلي.
احذر من الأخطاء الإملائية
عندما تقوم بنسخ الكود من هذه المقالة، انتبه للأخطاء الإملائية. على سبيل المثال، كائن StandardWatchEventKinds في القائمة 1 موجود بصيغة الجمع. حتى في وثائق Java.net تم كتابتها بشكل خاطئ.
نصائح
تعتبر آلية الإشعارات في NIO أسهل في الاستخدام من طريقة الاقتراع القديمة، مما سيدفعك إلى تجاهل التحليل التفصيلي لمتطلبات محددة. عندما تستخدم المستمع لأول مرة، عليك أن تفكر بعناية في دلالات المفاهيم التي تستخدمها. على سبيل المثال، معرفة متى سينتهي التغيير أكثر أهمية من معرفة متى يبدأ. يجب أن يكون هذا النوع من التحليل حذرًا للغاية، خاصة بالنسبة للسيناريوهات الشائعة مثل نقل مجلدات FTP. NIO هي حزمة قوية جدًا، ولكنها تحتوي أيضًا على بعض "المشاكل" الخفية التي يمكن أن تسبب ارتباكًا لمن لا يعرفونها.
2. المحددات والإدخال والإدخال غير المتزامن: تحسين تعدد الإرسال من خلال المحددات
الوافدون الجدد إلى NIO يربطونه عمومًا بـ "الإدخال/الإخراج غير المحظور". NIO هو أكثر من مجرد إدخال/إخراج غير محظور، ولكن هذا التصور ليس خاطئًا تمامًا: الإدخال/الإخراج الأساسي في Java يحظر الإدخال/الإخراج - مما يعني أنه ينتظر حتى تكتمل العملية - ومع ذلك، الإدخال/الإخراج غير المحظور أو غير المتزامن هي الميزة الأكثر استخدامًا في NIO، وليس كل NIO.
إن عمليات الإدخال/الإخراج غير المحظورة لـ NIO تعتمد على الأحداث ويتم توضيحها في مثال الاستماع لنظام الملفات في القائمة 1. وهذا يعني تحديد محدد (رد اتصال أو مستمع) لقناة الإدخال/الإخراج، ومن ثم يمكن للبرنامج مواصلة التشغيل. عند حدوث حدث على هذا المحدد - مثل تلقي سطر من الإدخال - "يستيقظ" المحدد وينفذ. يتم تنفيذ كل هذا من خلال مؤشر ترابط واحد، والذي يختلف بشكل كبير عن الإدخال/الإخراج القياسي في Java.
تعرض القائمة 2 برنامج صدى برنامج شبكة متعدد المنافذ تم تنفيذه باستخدام محدد NIO. هذا برنامج صغير أنشأه جريج ترافيس في عام 2003 (راجع قائمة الموارد). لقد نفذت الأنظمة المشابهة لنظام يونكس ويونكس منذ فترة طويلة محددات فعالة، وهي نموذج مرجعي جيد لنماذج البرمجة عالية الأداء في شبكات جافا.
قائمة 2.NIO محدد
انسخ رمز الكود كما يلي:
importjava.io.*;
importjava.net.*;
importjava.nio.*;
importjava.nio.channels.*;
importjava.util.*;
publicclassMultiPortEcho
{
Privateintports[];
PrivateByteBufferechoBuffer=ByteBuffer.allocate(1024);
publicMultiPortEcho(intports[])throwsIOException{
this.ports=ports;
config_selector();
}
Privatevoidconfigure_selector()throwsIOException{
//Createanewselector
Selectorselector=Selector.open();
// Openalisteneroneachport,andregistereachone
//withtheselector
for(inti=0;i<ports.length;++i){
ServerSocketChannelssc=ServerSocketChannel.open();
ssc.configureBlocking(false);
ServerSocketss=ssc.socket();
InetSocketAddressaddress=newInetSocketAddress(ports[i]);
ss.bind(address);
SelectionKeykey=ssc.register(selector,SelectionKey.OP_ACCEPT);
System.out.println("Goingtolistenon"+ports[i]);
}
بينما (صحيح) {
intnum=selector.select();
SetselectedKeys=selector.selectedKeys();
Iteratorit=selectedKeys.iterator();
بينما(it.hasNext()){
SelectionKeykey=(SelectionKey)it.next();
إذا ((key.readyOps()&SelectionKey.OP_ACCEPT)
==مفتاح التحديد.OP_ACCEPT){
// قبول الاتصال الجديد
ServerSocketChannelssc=(ServerSocketChannel)key.channel();
SwitchChannelsc=ssc.accept();
sc.configureBlocking(false);
//أضف الاتصال الجديد إلى المحدد
SelectionKeynewKey=sc.register(selector,SelectionKey.OP_READ);
it.remove();
System.out.println("حصلت على اتصال من"+sc);
}elseif((key.readyOps()&SelectionKey.OP_READ)
==مفتاح التحديد.OP_READ){
//اقرأ البيانات
SwitchChannelsc=(SocketChannel)key.channel();
// صدى البيانات
intbytesEchoed=0;
بينما (صحيح) {
echoBuffer.clear();
intnumber_of_bytes=sc.read(echoBuffer);
إذا (عدد_البايت<=0){
استراحة؛
}
echoBuffer.flip();
sc.write(echoBuffer);
bytesEchoed+=number_of_bytes;
}
System.out.println("Echoed"+bytesEchoed+"from"+sc);
it.remove();
}
}
}
}
staticpublicvoidmain(Stringargs[])throwsException{
إذا (args.length<=0){
System.err.println("الاستخدام:javaMultiPortEchoport[portport...]");
System.exit(1);
}
intports[]=newint[args.length];
for(inti=0;i<args.length;++i){
ports[i]=Integer.parseInt(args[i]);
}
newMultiPortEcho(ports);
}
}
قم بتجميع هذا الرمز وابدأ تشغيله بأمر مشابه لـ javaMultiPortEcho80058006. بمجرد تشغيل هذا البرنامج بنجاح، ابدأ تشغيل telnet بسيط أو محاكي طرفي آخر للاتصال بالواجهات 8005 و8006. سترى أن هذا البرنامج يردد جميع الأحرف التي يتلقاها - وهو يفعل ذلك من خلال سلسلة رسائل Java.
3. المقطع: الوعد والواقع
في NIO، يمكن للقناة أن تمثل أي كائن يمكن قراءته وكتابته. ويتمثل دورها في توفير تجريد للملفات والمآخذ. تدعم قنوات NIO مجموعة متسقة من الأساليب بحيث لا تحتاج إلى إيلاء اهتمام خاص للكائنات المختلفة عند التشفير، سواء كان ذلك إخراجًا قياسيًا أو اتصالاً بالشبكة أو القناة قيد الاستخدام. ميزة القنوات هذه موروثة من التدفقات في الإدخال/الإخراج الأساسي لـ Java. توفر التدفقات قنوات IO محظورة تدعم الإدخال / الإخراج غير المتزامن.
غالبًا ما يوصى باستخدام NIO لأدائه العالي، ولكن بشكل أكثر دقة لأوقات استجابته السريعة. في بعض السيناريوهات، يكون أداء NIO أسوأ من أداء Java I/O الأساسي. على سبيل المثال، بالنسبة لقراءة وكتابة تسلسلية بسيطة لملف صغير، قد يكون الأداء الذي يتم تحقيقه ببساطة عن طريق الدفق أسرع مرتين إلى ثلاث مرات من تنفيذ التشفير المطابق القائم على القناة والموجه نحو الحدث. وفي الوقت نفسه، تكون القنوات غير المتعددة الإرسال - أي قناة منفصلة لكل خيط - أبطأ بكثير من القنوات المتعددة التي تسجل محدداتها في نفس الخيط.
الآن عندما تفكر في استخدام التدفقات أو القنوات، حاول أن تسأل نفسك الأسئلة التالية:
كم عدد كائنات الإدخال/الإخراج التي تحتاج إلى قراءتها وكتابتها؟
هل كائنات الإدخال/الإخراج المختلفة متسلسلة أم أنها يجب أن تحدث جميعها في نفس الوقت؟
هل تحتاج كائنات الإدخال/الإخراج إلى الاستمرار لفترة زمنية قصيرة أو طوال عمر العملية بالكامل؟
هل الإدخال/الإخراج الخاص بك مناسب للمعالجة في مؤشر ترابط واحد أو في عدة مؤشرات ترابط مختلفة؟
هل يبدو اتصال الشبكة والإدخال/الإخراج المحلي متماثلين، أم أن لهما أنماطًا مختلفة؟
يعد هذا التحليل من أفضل الممارسات عند تحديد ما إذا كنت تريد استخدام التدفقات أو القنوات. تذكر: NIO و NIO.2 ليسا بديلين للإدخال/الإخراج الأساسي، بل هما مكملان له.
4. رسم خرائط الذاكرة - يتم استخدام الفولاذ الجيد على الشفرة
أهم تحسن في الأداء في NIO هو تعيين الذاكرة. تعيين الذاكرة هو خدمة على مستوى النظام تتعامل مع جزء من الملف المستخدم في البرنامج كذاكرة.
هناك العديد من التأثيرات المحتملة لرسم خرائط الذاكرة، أكثر مما يمكنني تقديمه هنا. على مستوى أعلى، يمكن أن يجعل أداء الإدخال/الإخراج للوصول إلى الملفات يصل إلى سرعة الوصول إلى الذاكرة. غالبًا ما يكون الوصول إلى الذاكرة أسرع من الوصول إلى الملفات. القائمة 3 هي مثال بسيط لخريطة ذاكرة NIO.
القائمة 3. تعيين الذاكرة في NIO
انسخ رمز الكود كما يلي:
importjava.io.RandomAccessFile;
importjava.nio.MappedByteBuffer;
importjava.nio.channels.FileChannel;
publicclassmem_map_example{
Privatestaticintmem_map_size=20*1024*1024;
PrivatestaticStringfn="example_memory_mapped_file.txt";
publicstaticvoidmain(String[]args)throwsException{
RandomAccessFilememoryMappedFile=newRandomAccessFile(fn,"rw");
//Mappingafileintomery
MappedByteBufferout=memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE,0,mem_map_size);
//الكتابة إلىMemoryMappedFile
ل(inti=0;i<mem_map_size;i++){
out.put((بايت)'A');
}
System.out.println("File'"+fn+"'isnow"+Integer.toString(mem_map_size)+"bytesfull.");
// قراءة من الذاكرة-mappedfile.
ل(inti=0;i<30;i++){
System.out.print((char)out.get(i));
}
System.out.println("/nReadingfrommemory-mappedfile'"+fn+"'مكتمل.");
}
}
في القائمة 3، يقوم هذا المثال البسيط بإنشاء ملف بحجم 20 ميجا example_memory_mapped_file.txt، ويملأه بالحرف A، ثم يقرأ أول 30 بايت. في التطبيقات العملية، لا يعد تعيين الذاكرة مفيدًا فقط في تحسين السرعة الأولية للإدخال/الإخراج، ولكنه يسمح أيضًا للعديد من القراء والكتاب المختلفين بمعالجة نفس صورة الملف في نفس الوقت. هذه التقنية قوية ولكنها خطيرة أيضًا، ولكن إذا تم استخدامها بشكل صحيح، فسوف تزيد من سرعة الإدخال/الإخراج لديك عدة مرات. كما نعلم جميعًا، تستخدم عمليات التداول في وول ستريت تقنية رسم خرائط الذاكرة من أجل الحصول على المزايا في ثوانٍ أو حتى أجزاء من الثانية.
5.تشفير الأحرف والبحث
الميزة الأخيرة لـ NIO التي أريد شرحها في هذه المقالة هي charset، وهي حزمة تستخدم لتحويل ترميزات الأحرف المختلفة. قبل NIO، نفذت Java معظم الوظائف نفسها المضمنة من خلال طريقة getByte. تحظى مجموعة الأحرف بشعبية لأنها أكثر مرونة من getBytes ويمكن تنفيذها على مستوى أقل، مما يؤدي إلى أداء أفضل. يعد هذا أكثر قيمة للبحث في اللغات غير الإنجليزية الحساسة للتشفير والترتيب وميزات اللغة الأخرى.
تعرض القائمة 4 مثالاً لتحويل أحرف Unicode في Java إلى Latin-1
القائمة 4. الشخصيات في NIO
انسخ رمز الكود كما يلي:
Stringsome_string="ThisisastringthatJavanativelystoresUnicode.";
Charsetlatin1_charset=Charset.forName("ISO-8859-1");
CharsetEncodelatin1_encoder=charset.newEncoder();
ByteBufferlatin1_bbuf=latin1_encoder.encode(CharBuffer.wrap(some_string));
لاحظ أن مجموعات الأحرف والقنوات مصممة للاستخدام معًا، بحيث يمكن تشغيل البرنامج بشكل طبيعي عند تنسيق تعيين الذاكرة والإدخال/الإخراج غير المتزامن وتحويل التشفير.
ملخص: بالطبع هناك المزيد لنعرفه
الغرض من هذه المقالة هو تعريف مطوري Java ببعض الميزات الأكثر أهمية (والمفيدة) في NIO وNIO.2. يمكنك استخدام الأساس الذي أنشأته هذه الأمثلة لفهم بعض أساليب NIO الأخرى، على سبيل المثال، يمكن أن تساعدك المعرفة التي تتعلمها عن القنوات في فهم معالجة الروابط الرمزية في نظام الملفات في مسار NIO. يمكنك أيضًا الرجوع إلى قائمة الموارد التي قدمتها لاحقًا، والتي توفر بعض المستندات للدراسة المتعمقة لواجهة برمجة تطبيقات الإدخال/الإخراج الجديدة في Java.