تنفيذ الخرائط باستخدام التعمية (Hashing) في جافا
يُعَدّ مفهوم الخرائط (Maps) القائم على التعمية أو «التجزئة» (Hashing) من اللبنات الجوهرية في بنية هياكل البيانات الحديثة ولُغات البرمجة عالية المستوى، ولا سيّما جافا. يهدف هذا المقال المطوَّل إلى الغوص بعمق في المبادئ النظرية والممارسات التطبيقية لبناء الخرائط المعمّاة، مع استعراض وافٍ لآلية عملها، وخوارزميات التجزئة، وتسوية التصادم (Collision Resolution)، وتفاصيل البنية الداخلية لفئة java.util.HashMap، والاعتبارات المتعلِّقة بالأداء، وتصميم خرائط آمنة ومتزامنة، إضافة إلى أفضل الممارسات لاختبارها وتحليل أثرها على إدارة الذاكرة في بيئة آلة جافا الافتراضية (JVM).
1. الخلفية النظرية للتعمية (Hashing)
1.1 تعريف دالة التجزئة
دالة التجزئة هي تابع حتمي يُحوِّل مدخَلاً ذا طول عشوائي إلى مخرَج بطول ثابت، يُعرف غالبًا باسم قيمة التجزئة (hash code). يشكِّل هذا المخرَج تمثيلًا عدديًا فريدًا (قدر الإمكان) للبيانات الأصلية، ويستخدم كمؤشِّر سريع لتحديد موضع العنصر داخل بنية تخزين ثابتة الحجم تدعى جدول التجزئة (Hash Table).
1.2 خصائص دالة التجزئة الجيدة
-
التوزيع المنتظم: يقلّ احتمال وقوع التصادمات عندما تنتشر قيم التجزئة بانتظام عبر المجال.
-
الحتمية: القيمة نفسها للمدخَل نفسه عبر جميع الاستدعاءات.
-
الكفاءة الحسابية: يجب أن تكون تكلفة حساب التجزئة صغيرة مقارنةً بعمليات الإدخال والبحث.
-
ضعف التنبؤ العكسي: لا يمكن استرجاع المدخَل الأصلي بسهولة من قيمة التجزئة (مطلب أمني).
2. بنية جدول التجزئة في جافا
2.1 تمثيل البيانات
في جافا، تُخزَّن أزواج المفتاح–القيمة ضمن مصفوفة دلائل (array of buckets) يُشار إليها عادةً بمصفوفة سطلية. يمثّل كل سطل (bucket) رأس قائمة مترابطة أو شجرة حمراء–سوداء (Red–Black Tree) تبعًا لكثافة التصادم.
2.2 الحساب الداخلي لفهرس السطل
يُستخرج الفهرس كالآتي:
javaint index = (n - 1) & hash; // n هو طول المصفوفة
يستفيد هذا التعبير من خاصية «والد» العملية الثنائية (bitwise AND) لاختزال قيمة التجزئة إلى حدود طول المصفوفة–القوة (عدد عناصرها قوة اثنين).
3. خوارزميات تسوية التصادم
3.1 السلسلة المتربطة (Separate Chaining)
يُخصَّص لكل سطل قائمة (أو شجرة) تُخزَّن فيها جميع العناصر المتصادمة. يسهل التنفيذ لكنه يستهلك ذاكرة إضافية للمؤشرات.
3.2 العنوان المفتوح (Open Addressing)
توضع العناصر مباشرةً داخل المصفوفة ويُعاد الحساب في حال التصادم وفق مخططات مثل:
-
التجاور الخطي (Linear Probing)
-
التجاور التربيعي (Quadratic Probing)
-
تجزئة مزدوجة (Double Hashing)
| خوارزمية | زمن البحث في الحالة المتوسطة | زمن البحث في أسوأ الحالات | استهلاك الذاكرة | التعقيد في التنفيذ |
|---|---|---|---|---|
| Separate Chaining | O(1 + α) | O(n) | مرتفع نسبيًا | بسيط |
| Linear Probing | O(1 / (1-α)) | O(n) | أقل | أبسط |
| Double Hashing | O(1 / (1-α)) | O(n) | أقل | متوسط |
α يرمز إلى معامل التحميل (load factor).
4. فئة HashMap في مكتبة Java Collections Framework
4.1 التركيب الداخلي
-
مصــفوفة Node
: تمثّل السطول. -
قيمة معامل التحميل الافتراضي (
DEFAULT_LOAD_FACTOR = 0.75f). -
إعادة التحجيم (Resizing): عند تجاوز α الحد الأقصى، تُنشأ مصفوفة مضاعفة الحجم ويُعاد توزيع العناصر.
4.2 الانتقال من قائمة إلى شجرة
اعتبارًا من جافا 8، إذا تجاوز طول قائمة السطل 8 عناصر وتجاوز حجم المصفوفة 64، تتحوّل القائمة إلى شجرة حمراء–سوداء، ما يخفض زمن البحث من O(n) إلى O(log n) في السطل الواحد.
4.3 دالة التجزئة الثانوية
تستخدم HashMap عملية خلط ثانوية (hash spreading) لتقليل التصادمات عالية المستوى الناجمة عن سوء تنفيذ hashCode() في الفئة المفتاحية.
5. الاعتبارات الأمنية
5.1 هجمات التصادم المتعمَّد
قد يحاول المهاجم حقن مفاتيح تُنتِج قيم تجزئة متصادمة، مؤدّيًا إلى تدهور الأداء نحو O(n) وإحداث شلل في الخدمة. لجافا آليتان رئيسيتان للحماية:
-
تجزئة عشوائية للمفاتيح النصية: إدخال مصفوفة بذور سرية تربك المهاجم.
-
التحويل إلى شجرة: يقلّل من أثر التصادمات الكثيفة.
6. الخرائط المتزامنة
6.1 Collections.synchronizedMap مقابل ConcurrentHashMap
-
الأول يطبّق قفلًا أحاديًا على الخريطة كاملةً، ما يحدّ من التوازي.
-
الثاني يقسّم البنية إلى مقاطع (segments) ويستخدم تجزئة مُجمّعة (striped locking) أو آليات اللا قفل (CAS) بعد جافا 8.
6.2 تحسين الأداء في البيئات متعددة الخيوط
-
اختيار حجم مبدئي مناسب يقلّل إعادة التحجيم.
-
ضبط معامل التحميل.
-
تفعيل
-XX:+UseNUMAلتحسين التموضع في الأنظمة متعددة المقابس.
7. اعتبارات إدارة الذاكرة في JVM
7.1 تأثير البكسلة (Boxing)
عند استخدام مفاتيح أو قيم من الأنواع الابتدائية، يحدث تغليف (auto‑boxing) يضيف حملًا على الذاكرة. للتقليل:
-
استخدام
Int2ObjectOpenHashMapمن fastutil أوInt2IntMapعندما يناسب.
7.2 تسرب الذاكرة
ينشأ عندما تُزال مراجع المفاتيح لكن تُحتفَظ بدخلها داخل السطول نتيجة سوء تطبيق equals/hashCode. الحلّ يكمن في تنفيذ الطريقتين تنفيذًا صحيحًا ومتناسقًا.
8. تصميم دالة hashCode() مثالية
8.1 المبادئ العامة
-
استخدام أعداد أولية في مزيج الحقول.
-
إدراج جميع الحقول الجوهرية في الحساب.
-
الالتزام بعقد
hashCode↔equals.
8.2 مثال تطبيقي
java@Override
public int hashCode() {
int result = 17;
result = 31 * result + id;
result = 31 * result + (name == null ? 0 : name.hashCode());
return result;
}
9. اختبار الأداء
9.1 أدوات القياس
-
JMH لضبط micro‑benchmarks.
-
VisualVM لرسم خريطة استهلاك الذاكرة.
9.2 سيناريو مختبر
-
إنشاء خريطتين:
HashMapوConcurrentHashMap. -
إدراج مليون عنصر عشوائي.
-
قياس زمن الإدراج والبحث عند α تتراوح بين 0.3 و0.9.
أظهرت النتائج انخفاضًا خطيًّا في أداءHashMapبعد تجاوز α = 0.75، في حين حافظتConcurrentHashMapعلى استقرار نسبي بفضل التجزئة المقسَّمة.
10. أفضل الممارسات (Best Practices)
-
تهيئة السعة المبدئية بالاستناد إلى حجم البيانات المتوقَّع.
-
الاحتفاظ بمعامل تحميل ≤ 0.75 لتحقيق توازن بين الأداء واستغلال الذاكرة.
-
استبدال القيم بكائنات ثابتة لتقليل ضغط جامع القمامة.
-
الاعتماد على
Map.of(...)للخرائط غير القابلة للتعديل في جافا 9+ حيث أمكن.
11. الخلاصة
يُعدّ تنفيذ الخرائط باستخدام التعمية في جافا تقنية راسخة تؤمّن عمليات إدراج وبحث وحذف شبه فورية عند الالتزام بقواعد التصميم السليم. إنّ فهم بنية HashMap وخواص التجزئة وأنماط تسوية التصادم، والاهتمام بالجوانب الأمنية والتزامنية، كفيل برفع موثوقية التطبيقات وأدائها، سواء في بيئات أحادية الخيط أو موزَّعة على خوادم متعدِّدة.
المراجع
-
Oracle. Java Platform, Standard Edition Documentation. Accessed 2025.
-
Doug Lea. Concurrent Programming in Java: Design Principles and Patterns, 2nd ed., Addison‑Wesley, 2000.

