الـ HashMap.get(key) اللي بياخد عادةً 80 نانو ثانية ممكن يطوّل فجأة لـ 8 ثواني على نفس الـ map وبنفس عدد العناصر. المشكلة مش في حجم البيانات، المشكلة في اصطدام الهاش (Hash Collision). المقال ده هيفكّلك ليه ده بيحصل، إزاي مهاجم ممكن يستغله لإسقاط سيرفر كامل، وإمتى لازم تقلق من الموضوع فعلاً.
Hash Collisions: ليه الـ HashMap بيتحوّل من O(1) لـ O(N) فجأة
المشكلة باختصار
كل الكود اللي بيستخدم dict في Python أو HashMap في Java بيفترض إن get و put بياخدوا وقت ثابت. في 99% من الحالات ده صحيح. لكن الـ 1% الباقية بتشمل حالتين خطيرتين: hash function ضعيفة بتوزّع العناصر بشكل سيء، أو مهاجم بيبعت مفاتيح متعمّدة كلها بتنزل في نفس الـ bucket. النتيجة: CPU بيتكهرب في loop خطّي جوّا bucket واحد.
تخيّل مكتبة فيها 10 أدراج: الفكرة لطفل عنده 7 سنين
افرض إن عندك مكتبة فيها 10 أدراج مرقّمة من 0 لـ 9. كل ما تيجي تحط كتاب، بتبص على أول حرف من اسمه وبتقرر الدرج رقم كام. لو الكتاب اسمه "أحمد" تحطه في درج 3، ولو اسمه "سارة" تحطه في درج 7. العملية سهلة: "أدّيني كتاب أحمد"، ببص على الحرف الأول، أفتح درج 3، ألاقيه. ثانية واحدة.
دلوقتي تخيّل إن فيه 100 كتاب كلهم بيبدأوا بحرف "أ". إيه اللي بيحصل؟ درج 3 بقى فيه 100 كتاب، والباقي فاضيين. لما تقوللي "هات كتاب أحمد"، مبقتش تفتح الدرج وتلاقيه؛ لازم تقرا عنوان كل كتاب في الدرج لحد ما تلاقيه. بدل ثانية بقت دقيقة. ده بالظبط اللي بيحصل في HashMap.
الشرح العلمي: Hash Function و Bucket Array
الـ HashMap في الخلفية عبارة عن مصفوفة (array) من الـ buckets — بالظبط زي الأدراج. كل bucket فيه مكان لعنصر أو لسلسلة (linked list) من العناصر. لما بتعمل map.put("ahmed", 42)، بيحصل 3 خطوات:
- الـ hash function بتاخد المفتاح
"ahmed"وبترجّع رقم صحيح كبير (مثلًا0x8A3F1C). - الرقم ده بيتقسم على عدد الـ buckets باستخدام
index = hash % capacity. الناتج هو رقم الدرج. - العنصر بيتحط في الدرج ده. لو الدرج فاضي، الإدخال
O(1). لو فيه عنصر قبلك (اصطدام)، بتتضاف في نهاية سلسلة الـ bucket.
الـ get بيمشي بنفس المنطق: hash → index → ابحث في الـ bucket. التعقيد المتوقّع O(1 + n/k) حيث n عدد العناصر و k عدد الـ buckets. طول ما n/k (اللي اسمه load factor) صغير، الأداء ثابت. لكن لما كل الـ n ينزلوا في bucket واحد، n/k بيساوي n فعليًا، والـ lookup بقى O(N).
Chaining و Open Addressing: إزاي الـ runtime بيحلّ الاصطدام
فيه استراتيجيتين أساسيتين لتعامل الـ HashMap مع الاصطدام: