الألعاب النارية! أن تكون مبرمجًا ليس بالأمر السهل. عليك أن تتعلم باستمرار، وأن تتعلم دائمًا شيئًا جديدًا. ولكن، كما هو الحال في أي عمل آخر، فإن أصعب شيء هو البدء، واتخاذ الخطوة الأولى نحو هدفك. وبما أنك جالس على هذا الموقع وتقرأ هذا المقال، فقد أكملت الخطوة الأولى. هذا يعني أنك الآن بحاجة إلى التحرك بشكل هادف نحو هدفك، دون التباطؤ أو التوقف على طول الطريق. إذا فهمت بشكل صحيح، فإن هدفك هو أن تصبح مطور Java أو تعزيز معرفتك إذا كنت واحدًا. إذا كان الأمر كذلك، فأنت في المكان الصحيح، لأننا سنستمر في تحليل قائمة واسعة تضم أكثر من 250 سؤالًا لمقابلة مطوري Java. فلنكمل!
المجموعات
84. أخبرنا عن التكرارات واستخدامها
تعد المجموعات أحد الموضوعات المفضلة في أي مقابلة لمطوري Java، وعند الحديث عن التسلسل الهرمي للمجموعات، غالبًا ما يقول المرشحون إنها تبدأ بواجهة المجموعة . ولكن هذا ليس صحيحا، لأنه فوق هذه الواجهة هناك واجهة أخرى - Iterable . تمثل هذه الواجهة طريقة iterator() ، والتي تسمح لك باستدعاء كائن Iterator للمجموعة الحالية. وما هو كائن Iterator هذا بالضبط ؟ التكرار هو كائن يوفر القدرة على التنقل عبر مجموعة والتكرار على العناصر دون أن يحتاج المستخدم إلى معرفة تنفيذ مجموعة معينة. وهذا هو، هذا هو نوع من المؤشر لعناصر المجموعة، والتي تبدو وكأنها مكان معين فيها. يحتوي المكرر على الطرق التالية:- hasNext() - يُرجع صحيحًا إذا كان هناك عنصر يقع بعد المؤشر (تسمح لك هذه الطريقة بمعرفة ما إذا كان قد تم الوصول إلى نهاية المجموعة)؛
- next() - يُرجع العنصر التالي بعد المؤشر. إذا لم يكن هناك أي شيء، فسيتم طرح NoSuchElementException . أي أنه من الأفضل التأكد من وجود العنصر قبل استخدام هذه الطريقة - باستخدام hasNext() ;
- إزالة () - إزالة العنصر الأخير المستلم من المجموعة باستخدام الطريقة التالية () . إذا لم يتم استدعاء next() مطلقًا قبل استدعاء إزالة() ، فسيتم طرح استثناء - IllegalStateException ؛
- forEachRemaining(<Consumer>) - ينفذ الإجراء الذي تم تمريره مع كل عنصر من عناصر المجموعة (ظهرت الطريقة في Java 8).
List<String> list = new ArrayList<>();
list.add("Hello ");
list.add("World, ");
list.add("It's ");
list.add("Amigo!");
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
iterator.next();
iterator.remove();
}
System.out.println(list.size());
ستعرض وحدة التحكم:
0
وهذا يعني أن إزالة العناصر كانت ناجحة. بمجرد حصولنا على مُكرِّر، يمكننا استخدام طريقة لطباعة جميع العناصر على الشاشة:
iterator.forEachRemaining(x -> System.out.print(x));
ولكن بعد ذلك، سيصبح المُكرِّر غير مناسب لمزيد من الاستخدام، لأنه سيجتاز القائمة بأكملها، ولا يمتلك المُكرِّر العادي طرقًا للتراجع. نحن هنا نقترب تدريجيًا من LinkedList ، أي طريقة listIterator() الخاصة به ، والتي تُرجع نوعًا حديثًا من المكرر - ListIterator . بالإضافة إلى طرق التكرار العادية (القياسية)، تحتوي هذه الطريقة على طرق إضافية:
- add(<Element>) - يُدرج عنصرًا جديدًا في القائمة؛
- hasPrevious() - تُرجع صحيحًا إذا كان هناك عنصر يقع قبل المؤشر (سواء كان هناك عنصر سابق)؛
- nextIndex() - يُرجع الفهرس في قائمة العنصر التالي بعد المؤشر؛
- السابق () - يُرجع العنصر السابق (حتى المؤشر)؛
- PreviousIndex() - يُرجع فهرس العنصر السابق؛
- set(<Element>) - يستبدل العنصر الأخير الذي تم إرجاعه بواسطة الطرق التالية () أو السابقة () .
85. ما هو التسلسل الهرمي للمجموعة في Java Collection Framework؟
هناك نوعان من التسلسلات الهرمية للمجموعات في Java. التسلسل الهرمي الأول هو التسلسل الهرمي للمجموعات نفسها مع البنية التالية: وهي بدورها مقسمة إلى المجموعات الفرعية التالية:- المجموعة عبارة عن واجهة تصف بنية البيانات كمجموعة تحتوي على عناصر فريدة غير مرتبة (غير متكررة). تحتوي الواجهة على تطبيقات قياسية - TreeSet و HashSet و LinkedHashSet .
- القائمة عبارة عن واجهة تصف بنية البيانات التي تخزن تسلسلًا مرتبًا للكائنات. يمكن إدراج المثيلات الموجودة في القائمة وحذفها بواسطة فهرسها في هذه المجموعة (مماثل للمصفوفة، ولكن مع تغيير الحجم الديناميكي). تحتوي الواجهة على تطبيقات قياسية - ArrayList و Vector ( التي تعتبر قديمة وغير مستخدمة فعليًا ) و LinkedList .
- قائمة الانتظار عبارة عن واجهة تصف بنية البيانات التي تخزن العناصر في شكل قائمة انتظار تتبع قاعدة FIFO - أول ما يدخل أولاً يخرج . تحتوي الواجهة على التطبيقات القياسية التالية: LinkedList (نعم، إنها تنفذ قائمة الانتظار أيضًا) و PriotityQueue .
86. ما هو الهيكل الداخلي لقائمة ArrayList؟
ArrayList يشبه المصفوفة، ولكن مع إمكانية التوسع ديناميكيًا. ماذا يعني ذلك؟ الحقيقة هي أن ArrayList يعمل على أساس مصفوفة عادية، أي أنه يخزن العناصر في مصفوفة داخلية (حجمها الافتراضي هو 10 خلايا). عندما يمتلئ المصفوفة الداخلية، يتم إنشاء مصفوفة جديدة، يتم تحديد حجمها بواسطة الصيغة:<размерТекущегоМассива> * 3 / 2 + 1
أي أنه إذا كان حجم المصفوفة لدينا هو 10، فسيكون حجم المصفوفة الجديدة: 10 * 3 / 2 + 1 = 16. بعد ذلك، يتم نسخ كافة القيم من المصفوفة الأولى (القديمة) إليها باستخدام الأمر طريقة System.arraycopy () الأصلية ، ويتم حذف المصفوفة الأولى. في الواقع، هذه هي الطريقة التي يتم بها تنفيذ التوسعة الديناميكية لـ ArrayList . دعونا نلقي نظرة على أساليب ArrayList الأكثر استخدامًا : 1. add(<Elelement>) - يضيف عنصرًا إلى نهاية المصفوفة (إلى آخر خلية فارغة)، ويتحقق أولاً مما إذا كانت هناك مساحة في هذه المصفوفة. إذا لم يكن هناك، فسيتم إنشاء مصفوفة جديدة يتم نسخ العناصر إليها. التعقيد اللوغاريتمي لهذه العملية هو O(1). هناك طريقة مشابهة - add(<Index>,<Elelement>) . فهو يضيف عنصرًا ليس إلى نهاية القائمة (المصفوفة)، بل إلى خلية معينة تحتوي على الفهرس الذي يأتي كوسيطة. في هذه الحالة، سيختلف التعقيد اللوغاريتمي اعتمادًا على مكان إضافته:
- إذا كانت هذه بداية القائمة تقريبًا، فسيكون التعقيد اللوغاريتمي قريبًا من O(N)، لأن جميع العناصر الموجودة على يمين العنصر الجديد يجب نقلها خلية واحدة إلى اليمين؛
- إذا تم إدراج العنصر في المنتصف - O(N/2) لأن نحن بحاجة إلى نقل نصف عناصر القائمة فقط خلية واحدة إلى اليمين.
87. ما هو الهيكل الداخلي للLinkedList؟
إذا كانت ArrayList تحتوي على عناصر في مصفوفة داخلية، فإن LinkedList تكون في شكل قائمة مرتبطة بشكل مزدوج. وهذا يعني أن كل عنصر يحتوي على رابط للعنصر السابق ( السابق ) والعنصر التالي ( التالي ). العنصر الأول ليس له رابط للعنصر السابق (هو الأول)، لكنه يعتبر رأس القائمة، والقائمة المرتبطة لها رابط مباشر إليها. العنصر الأخير، في الواقع، ليس له عنصر تالي، فهو ذيل القائمة، وبالتالي يوجد رابط مباشر إليه في LinkedList نفسها . ولذلك، فإن التعقيد اللوغاريتمي للوصول إلى رأس القائمة أو ذيلها هو O(1). في ArrayList، عندما نمت القائمة، زاد المصفوفة الداخلية، ولكن هنا يحدث كل شيء ببساطة أكثر - عند إضافة عنصر، يتغير زوج من الروابط ببساطة. دعونا نلقي نظرة على بعض طرق LinkedlList الأكثر استخدامًا : 1. add(<Elelement>) - الإضافة في نهاية القائمة، أي: بعد العنصر الأخير (5) سيتم إضافة رابط للعنصر الجديد على النحو التالي . سيكون للعنصر الجديد رابط للعنصر الأخير (5) كالعنصر السابق . سيكون التعقيد اللوغاريتمي لمثل هذه العملية هو O(1)، نظرًا لأن هناك حاجة فقط إلى رابط للعنصر الأخير، وكما تتذكر، يحتوي الذيل على رابط مباشر من LinkedList والتعقيد اللوغاريتمي للوصول إليه ضئيل. 2. add(<Index>,<Elelement>) - إضافة عنصر حسب الفهرس. عند إضافة عنصر، على سبيل المثال، إلى منتصف القائمة، يتم تكرار العناصر من الرأس والذيل (على كلا الجانبين) أولاً حتى يتم العثور على المكان المطلوب. إذا أردنا إدراج عنصر بين العنصر الثالث والرابع (في الشكل أعلاه)، فعند البحث عن المكان الصحيح، سيشير الرابط التالي للعنصر الثالث بالفعل إلى العنصر الجديد. بالنسبة للرابط الجديد، فإن الرابط السابق سيشير إلى الرابط الثالث. وعليه، فإن رابط العنصر الرابع - السابق - سيشير بالفعل إلى العنصر الجديد، وسيشير الرابط التالي للعنصر الجديد إلى العنصر الرابع: وسيعتمد التعقيد اللوغاريتمي لهذه الطريقة على الفهرس المعطى للعنصر الجديد:- إذا كان قريبًا من الرأس أو الذيل، فسوف يقترب من O(1)، لأنه لن يكون من الضروري فعليًا التكرار على العناصر؛
- إذا كان قريبًا من المنتصف، فسيتم فرز العناصر من الرأس والذيل في وقت واحد حتى يتم العثور على العنصر المطلوب.
88. ما هو الهيكل الداخلي لـ HashMap؟
ربما يكون أحد الأسئلة الأكثر شيوعًا عند إجراء مقابلة مع مطور Java. يعمل HashMap v مع أزواج القيمة الرئيسية . كيف يتم تخزينها داخل HashMapv نفسها ؟ يوجد داخل HashMap مجموعة من العقد:Node<K,V>[] table
افتراضيًا، حجم المصفوفة هو 16، ويتضاعف في كل مرة يتم ملؤها بالعناصر (عند الوصول إلى LOAD_FACTOR - نسبة معينة من الامتلاء، تكون افتراضيًا 0.75 ). تقوم كل عقدة بتخزين تجزئة المفتاح، ومفتاح، وقيمة، ورابط إلى العنصر التالي: في الواقع، "رابط إلى العنصر التالي" يعني أننا نتعامل مع قائمة مرتبطة بشكل فردي، حيث يحتوي كل عنصر على رابط إلى التالي. أي أن HashMap يخزن البيانات في مجموعة من القوائم المرتبطة بشكل فردي. لكنني سألاحظ على الفور: عندما تحتوي خلية واحدة من مصفوفة الجدول على رابط لقائمة مرتبطة منفردة مماثلة تتكون من أكثر من عنصر واحد، فهذا ليس جيدًا. وتسمى هذه الظاهرة الاصطدام . ولكن أول الأشياء أولا. دعونا نرى كيف يتم حفظ زوج جديد باستخدام طريقة الوضع . أولاً، يتم أخذ hachCode() للمفتاح. لذلك، لكي يعمل hashmap بشكل صحيح ، عليك أن تأخذ دروسًا يتم فيها تجاوز هذه الطريقة كمفاتيح. يتم بعد ذلك استخدام رمز التجزئة هذا في الطريقة الداخلية - hash() - لتحديد الرقم الموجود ضمن حجم مصفوفة الجدول . بعد ذلك، باستخدام الرقم المستلم، يتم الوصول إلى خلية معينة من مصفوفة الجدول . وهنا لدينا حالتان:
- الخلية فارغة - يتم تخزين قيمة العقدة الجديدة فيها .
- الخلية ليست فارغة - تتم مقارنة قيمة المفاتيح. إذا كانت متساوية، فإن قيمة العقدة الجديدة تحل محل القيمة القديمة، وإذا لم تكن متساوية، فسيتم الوصول إلى العنصر التالي ومقارنته بمفتاحه... وهكذا حتى تحل القيمة الجديدة محل القيمة القديمة أو تصل إلى نهاية العنصر قائمة مرتبطة بشكل فردي وسيتم تخزينها هناك كعنصر أخير.
GO TO FULL VERSION