HashMap مقابل Hashtable في جافا: الاختلافات والبنية الداخلية

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

HashMap vs Hashtable في جافا: من ينتصر؟ نتعمق تحت الغطاء

\n\n

إذا سبق لك أن تساءلت: "ما الفرق بين HashMap و Hashtable؟" — فأنت لست وحدك. هذا أحد أكثر الأسئلة شيوعًا على Stack Overflow، وليس بدون سبب. كلا الفئتين تخزنان البيانات بتنسيق "مفتاح — قيمة"، وكلاهما يستخدم التجزئة، لكنهما يعيشان في عالمين مختلفين. الأولى سريعة وعصرية، والثانية قديمة وموثوقة ولكنها بطيئة بعض الشيء. دعونا نلقي نظرة تحت الغطاء ونفهم ما الأمر.

\n\n

في هذه المقالة، لن نقوم فقط بسرد الاختلافات. بل سنفهم كيف تعمل الآلية الداخلية، ولماذا تسمح إحدى الفئتين بـ null بينما الأخرى لا، ومتى يجب استخدام ConcurrentHashMap بدلاً من كليهما.

\n\n

نبذة تاريخية سريعة

\n\n

Hashtable ظهر في جافا منذ الإصدار 1.0. كانت واحدة من أولى فئات المجموعات، وكانت متزامنة منذ البداية. في تلك الأيام، كان تعدد الخيوط نادرًا، لكن مطوري جافا قرروا توخي الحذر. لاحقًا، في جافا 2 (1.2)، ظهرت مجموعات جديدة من إطار عمل المجموعات Collections Framework، ومن بينها HashMap. كانت غير متزامنة، لكنها أسرع بكثير. منذ ذلك الحين، تعتبر Hashtable قديمة (legacy)، على الرغم من عدم إزالتها من اللغة.

\n\n

الفرق الرئيسي: المزامنة والخيوط

\n\n

الفرق الأول والأهم هو المزامنة. طرق Hashtable (مثل put() و get() و remove()) موسومة بالكلمة المفتاحية synchronized. هذا يعني أن خيطين لا يمكنهما تنفيذ نفس الطريقة في نفس الوقت على كائن Hashtable واحد. يبدو هذا كميزة، لكنه في الواقع يتحول إلى عنق زجاجة.

\n\n
// Hashtable — جميع الطرق متزامنة\nHashtable<String, String> table = new Hashtable<>();\ntable.put("key1", "value1"); // آمن للخيوط، لكنه بطيء في ظل المنافسة العالية\n\n// HashMap — غير متزامن\nHashMap<String, String> map = new HashMap<>();\nmap.put("key1", "value1"); // سريع، لكنه غير آمن للخيوط\n
\n\n

إذا كنت تعمل في تطبيق أحادي الخيط (وهو الغالب)، فإن HashMap سيعمل بشكل أسرع، لأنه لا يحتاج إلى إضاعة الوقت في الحصول على الأقفال وتحريرها. أما إذا كنت بحاجة إلى أمان الخيوط، فمن الأفضل عدم استخدام Hashtable، بل استخدام ConcurrentHashMap (الذي ظهر في جافا 5). يستخدم هذا الأخير قفلًا أكثر دقة (قطاعات أو أقفال على مستوى الدلاء) ويعمل بشكل أسرع بكثير في بيئة متعددة الخيوط.

\n\n

Null، null، و null مرة أخرى

\n\n

الفرق المهم الثاني هو التعامل مع null. يسمح HashMap باستخدام null كمفتاح (مرة واحدة فقط) وكقيم (أي عدد من المرات). بينما سيرمي Hashtable استثناء NullPointerException إذا حاولت تمرير null.

\n\n
HashMap<String, String> map = new HashMap<>();\nmap.put(null, "nullKey"); // مسموح\nmap.put("key", null);     // مسموح\nSystem.out.println(map.get(null)); // "nullKey"\n\nHashtable<String, String> table = new Hashtable<>();\n// table.put(null, "value"); // NullPointerException!\n// table.put("key", null);   // NullPointerException!\n
\n\n

لماذا هذا؟ الأمر كله يتعلق بكيفية عمل التجزئة. في HashMap، يتم استخدام رمز تجزئة خاص بقيمة 0 للمفتاح null، ويتم وضعه في الدلو الصفري (bucket). بينما يستخدم Hashtable آلية قديمة، حيث يؤدي استدعاء hashCode() على null إلى خطأ. بالإضافة إلى ذلك، لا يعتمد Hashtable على القيم null، لأن ذلك سينتهك منطق العمليات المتزامنة.

\n\n

البنية الداخلية: كيف يعمل جدول التجزئة تحت الغطاء

\n\n

لفهم الفرق بشكل أعمق، دعونا نلقي نظرة داخلية. يعتمد كل من HashMap و Hashtable على جدول التجزئة. وهو عبارة عن مصفوفة من "الدلاء"، حيث كل دلو عبارة عن قائمة مرتبطة (أو شجرة، بدءًا من جافا 8). عند إضافة

المدونات

توصيات الكتب