ملخص
في لغة Java ، يتم توفير إطار عمل لجمع البيانات ، والذي يحدد بعض أنواع البيانات المجردة مثل القائمة والتعيين. كل نوع بيانات مجردة له تطبيق خاص به ، وتتبنى الطبقة الأساسية طرق تنفيذ مختلفة ، مثل ArrayList و LinkedList.
بالإضافة إلى ذلك ، توفر Java أيضًا عدة طرق مختلفة لاجتياز مجموعات البيانات. يجب على المطورين فهم الخصائص والمناسبات القابلة للتطبيق والأداء في التطبيقات الأساسية المختلفة. دعنا نحلل هذا المحتوى بالتفصيل أدناه.
كيف يتم تخزين عناصر البيانات في الذاكرة؟
هناك طريقتان رئيسيتان للتخزين لعناصر البيانات في الذاكرة:
1. التخزين المتسلسل ، الوصول العشوائي (الوصول المباشر):
وبهذه الطريقة ، يتم تخزين عناصر البيانات المجاورة في عناوين الذاكرة المجاورة ، وعنوان الذاكرة بأكمله مستمر. يمكن حساب عنوان الذاكرة مباشرة بناءً على موقع العنصر وقراءته مباشرة. متوسط تعقيد الوقت لقراءة عنصر في موقع معين هو O (1). عادة ، يتم تنفيذ المجموعات التي يتم تنفيذها على أساس المصفوفات هذه الميزة. يتم تمثيل ArrayList في Java.
2. تخزين السلسلة ، الوصول المتسلسل:
وبهذه الطريقة ، لا يلزم أن يكون كل عنصر من عناصر البيانات في وضع مجاور في الذاكرة ، ويحتوي كل عنصر بيانات على عنوان ذاكرة العنصر التالي. لا يمكن حساب عنوان الذاكرة مباشرة بناءً على موقع العنصر ، ولا يمكن قراءة العناصر إلا بالترتيب. متوسط تعقيد الوقت لقراءة عنصر في موقع معين هو o (n). يتم تمثيله بشكل رئيسي من خلال القوائم المرتبطة.
يتم تمثيل LinkedList في Java.
ما هي طرق اجتياز المقدمة في جافا؟
1.
يحافظ Traverser نفسه على عداد خارج المجموعة ، ثم يقرأ العناصر في كل موضع بالتسلسل ، ويتوقف عند قراءة العنصر الأخير. الشيء الرئيسي هو قراءة العنصر وفقًا لموقفه. هذه هي أيضا طريقة اجتياز التجميع الأكثر بدائية.
طريقة الكتابة هي:
لـ (int i = 0 ؛ i <list.size () ؛ i ++) {list.get (i) ؛}2.
كان ITerator في الأصل نمط تصميم OO ، مع الغرض الرئيسي من ذلك هو منع خصائص مجموعات البيانات المختلفة وتجاوز واجهات المجموعة بشكل موحد. كلغة OO ، تدعم Java بشكل طبيعي وضع Iterator في المجموعات.
طريقة الكتابة هي:
iterator iterator = list.iterator () ؛ بينما (iterator.hasnext ()) {iterator.next () ؛}3.
يتم حظر التكرار والعدادات التي تم إعلانها بشكل صريح.
المزايا: الرمز موجز وليس عرضة للأخطاء.
العيوب: يمكن القيام فقط بالمرور البسيط ، ولا يمكن تشغيل مجموعات البيانات (حذف ، استبدال) أثناء عملية اجتياز.
طريقة الكتابة هي:
لـ (elementType element: list) {}ما هو مبدأ التنفيذ لكل طريقة اجتياز؟
1.
يحافظ Traverser نفسه على عداد خارج المجموعة ، ثم يقرأ العناصر في كل موضع بالتسلسل ، ويتوقف عند قراءة العنصر الأخير. الشيء الرئيسي هو قراءة العنصر وفقًا لموقفه.
2.
كل مجموعة بيانات تنفيذ محددة تحتاج عمومًا إلى توفير مؤلف مقابل. بالمقارنة مع التقليدية للحلقات ، يحظر التكرار عدادات اجتياز صريحة. لذلك ، يمكن لـ Iterator استنادًا إلى التخزين المتسلسل للمجموعات الوصول مباشرة إلى البيانات حسب الموقع. يتطلب التكرار المستند إلى مجموعات تخزين السلسلة ، أن التطبيقات العادية تتطلب حفظ موقع اجتياز الحالي. ثم حرك المؤشر للأمام أو للخلف وفقًا للموقف الحالي.
3.
وفقًا لرمز BYTECODE الذي تم ترحيله ، يمكن العثور على أن FOREACH يستخدم أيضًا طريقة التكرار لتنفيذها ، لكن برنامج التحويل البرمجي Java ساعدنا في إنشاء هذه الرموز.
كيف تؤدي كل طريقة اجتياز لطرق التخزين المختلفة؟
1.
لأنه يعتمد على موضع العنصر ، اقرأ عن طريق الموضع. لذلك يمكننا أن نعلم أنه بالنسبة للتخزين المتسلسل ، لأن متوسط التعقيد الزمني لعناصر القراءة في موقع معين هو O (1) ، فإن متوسط التعقيد للوقت لاجتياز المجموعة بأكملها هو O (n). لتخزين السلسلة ، نظرًا لأن متوسط التعقيد الزمني لعناصر القراءة في موقع معين هو O (N) ، فإن متوسط التعقيد للوقت لاجتياز المجموعة بأكملها هو O (N2) (مربع N).
رمز ArrayList يقرأ بواسطة الموضع: اقرأ مباشرة حسب موضع العنصر.
كائن عابر [] elementData ؛ public e get (int index) {rangecheck (index) ؛ return elementData (index) ؛} e elementData (int index) {return (e) elementData [index] ؛}رمز LinkedList يقرأ حسب الموضع: في كل مرة تحتاج إلى قراءة للخلف من العنصر 0. في الواقع ، قام أيضًا بإجراء تحسينات صغيرة داخليًا.
عابر حجم int = 0 ؛ عقدة عابرة <e> أولاً ؛ العقدة المؤقتة <e> last ؛ public e get (int index) {checkElementIndex (index) ؛ return node (index) .item ؛} node <e> node (int index) {if (index <(size >> 1)) <الفهرس ؛2.
ثم ، لمجموعات من نوع العشوائي ، لا يوجد معنى كبير. بدلاً من ذلك ، ستضيف بعض العمليات الإضافية وقت تشغيل إضافي. ومع ذلك ، فهي ذات أهمية كبيرة بالنسبة لمجموعة الوصول المتسلسل ، لأن ITerator يحافظ على موضع اجتياز الحالي ، لذلك في كل مرة تقوم فيها بالتجاوز ، لا تحتاج إلى البدء في البحث عن العنصر الأول من المجموعة. فقط حرك المؤشر واحد تلو الآخر. وبهذه الطريقة ، يتم تقليل التعقيد الزمني لاجتياز المجموعة بأكملها إلى O (N) ؛
(يتم استخدام هذا LinkedList فقط كمثال) يتم تنفيذ التكرار لـ LinkedList داخليًا ، وهو الحفاظ على موضع اجتياز الحالي ، ثم تشغيل المؤشر للتحرك:
شفرة:
public e next () {checkforcomodification () ؛ if (! hasnext ()) رمي nosuchelementexception () ؛ lastreturned = التالي ؛ التالي = التالي. == فارغ)؟ أخيرًا: next.prev ؛ nextIndex-؛ return lastreturned.item ؛}3.
من خلال تحليل Java Bytecode ، يمكننا أن نرى أن مبدأ التنفيذ الداخلي لـ Foreach يتم تنفيذه أيضًا من خلال Iterator ، ولكن يتم إنشاء هذا الترجيح بواسطة مترجم Java بالنسبة لنا ، لذلك لا نحتاج إلى كتابته يدويًا. ومع ذلك ، نظرًا لأن اختبارات تحويل النوع مطلوبة في كل مرة ، فإنه يستغرق وقتًا أطول قليلاً من التكرار. تعقيد الوقت هو نفس التعقيد.
Bytecode باستخدام ITerator:
رمز: جديد # // class java/util/arraylistdupinvokespecial # // method java/util/arraylist. interfacemethod java/util/iterator.next :() ljava/lang/object ؛ popaload_invokeinterface #، // interfacemethod java/util/iterator.hasnext :()
استخدم Bytecode foreach:
رمز: جديد # // class java/util/arraylistdupinvokespecial # // method java/util/arraylist. interfacemethod java/util/iterator.next :() ljava/lang/object ؛ checcast # // class loop/modelastore_aload_invokeinterface # ،
في أي مناسبات تنطبق كل طريقة اجتياز؟
1.
التخزين المتسلسل: أداء قراءة عالية. مناسبة لمجموعات التخزين المتسلسلة.
تخزين السلسلة: تعقيد الوقت مرتفع للغاية وليس مناسبًا لاجتياز مجموعات تخزين السلسلة.
2.
التخزين المتسلسل: إذا كنت لا تهتم كثيرًا بالوقت ، فمن المستحسن اختيار هذه الطريقة. بعد كل شيء ، يكون الرمز أكثر إيجازًا ويمنع أيضًا مشكلة خارج عن طريق واحد.
تخزين السلسلة: إنها ذات أهمية كبيرة. يتم تقليل متوسط تعقيد الوقت إلى O (n) ، وهو أمر جذاب للغاية ، لذلك يوصى بهذه الطريقة.
3.
يجعل Foreach فقط الكود أكثر إيجازًا ، ولكن يحتوي على بعض العيوب ، أي أنه لا يمكن تشغيل مجموعات البيانات (الحذف ، إلخ) أثناء عملية اجتياز ، لذلك لا يتم استخدامه في بعض المناسبات. علاوة على ذلك ، يتم تنفيذها استنادًا إلى التكرار ، ولكن بسبب مشكلة تحويل النوع ، سيكون أبطأ قليلاً من استخدام التكرار مباشرة. لحسن الحظ ، تعقيد الوقت هو نفسه. لذا ، كيفية الاختيار ، ارجع إلى طريقتين أعلاه لاتخاذ خيار التسوية.
ما هي أفضل الممارسات لجافا؟
في إطار جمع بيانات Java ، يتم توفير واجهة RandomAccess ، والتي لا تحتوي على طرق ، مجرد علامة. عادة ما يتم استخدامه من خلال تنفيذ واجهة القائمة لتحديد ما إذا كان تنفيذ القائمة يدعم الوصول العشوائي.
تقوم مجموعة البيانات بتنفيذ هذه الواجهة ، مما يعني أنها تدعم الوصول العشوائي ، ومتوسط التعقيد الزمني لعناصر القراءة حسب الموقع هو O (1). على سبيل المثال ، ArrayList.
إذا لم يتم تنفيذ هذه الواجهة ، فهذا يعني أن الوصول العشوائي غير مدعوم. على سبيل المثال ، LinkedList.
لذلك يبدو أن مطوري JDK لاحظوا أيضًا هذه المشكلة. تتمثل الطريقة الموصى بها أولاً في تحديد ما إذا كان الوصول العشوائي مدعومًا ، أي قائمة electionof randomaccess.
على سبيل المثال:
if (list exateof randomaccess) {// استخدم التقليدية لتجاوز الحلقة. } آخر {// استخدم ITerator أو foreach. }ما ورد أعلاه هو تحليل طريقة جمع Java Traversal (مبدأ التنفيذ ، أداء الخوارزمية ، والمناسبات المعمول بها) التي قدمها المحرر. آمل أن يكون ذلك مفيدًا للجميع!