Delete key in map: كيف يعمل تحت الغطاء في Go
\n\nهل تساءلت يومًا ما الذي يحدث حقًا عندما تكتب delete(myMap, "key") في Go؟ هل يختفي المفتاح ببساطة من الذاكرة؟ أم تبقى "فجوة" هناك؟ وماذا لو حذفت مفتاحًا من nil-map؟ إذا كنت قد سألت نفسك هذه الأسئلة - فأنت لست وحدك. موضوع delete key in map جمع آلاف المشاهدات على Stack Overflow، واليوم سنلقي نظرة تحت غطاء هذا الإجراء الذي يبدو بسيطًا.
في هذه المقالة SEO، سنشرح كيف يعمل جدول التجزئة في Go (هيكل hmap)، وكيف يعمل حذف المفتاح، وأين تذهب الذاكرة، ولماذا لا يجب الخوف من delete في الحلقات. هيا بنا!
نبذة مختصرة: كيف تعمل map في Go
\n\nقبل الحديث عن الحذف، دعنا نتذكر كيف تخزن map golang البيانات. داخليًا، هو جدول تجزئة، هيكل hmap. يتكون من مصفوفة من "الدلاء" (buckets)، يمكن لكل منها تخزين ما يصل إلى 8 أزواج مفتاح-قيمة. عندما تضيف مفتاحًا، يحسب Go التجزئة، ويجد الدلو ويضع الزوج بداخله. إذا فاض الدلو، يتم إنشاء سلسلة فيضان (overflow bucket).
هذا تنفيذ كلاسيكي، ولكن مع بعض الحيل التي سنتحدث عنها أدناه.
\n\nهيكل hmap (مبسط)
\n\ntype hmap struct {\n count int // عدد العناصر\n flags uint8\n B uint8 // لوغاريتم عدد الدلاء\n buckets unsafe.Pointer // مؤشر لمصفوفة الدلاء\n oldbuckets unsafe.Pointer // عند نمو map\n nevacuate uintptr\n // ...\n}\n\nالمفتاح: buckets هي مصفوفة متصلة. الحذف لا يضغط هذه المصفوفة، بل يضع علامة على الخلية بأنها "فارغة".
Delete key in map: كيف يعمل على مستوى الكود
\n\nاستدعاء delete(m, k) في Go هو دالة مدمجة، يتم ترجمتها إلى استدعاء وقت التشغيل. داخليًا، يحدث ما يلي تقريبًا:
- \n
- حساب تجزئة المفتاح - يستخدم Go دالة تجزئة خاصة بنوع المفتاح. \n
- البحث عن الدلو - يتم تحديد رقم الدلو بواسطة البتات السفلية للتجزئة. \n
- البحث الخطي داخل الدلو (وسلاسل الفيضان) - يجد الخلية التي تحتوي على المفتاح المطلوب. \n
- تنظيف الذاكرة - يقوم Go بمسح الذاكرة الخاصة بالمفتاح والقيمة، ليتمكن جامع القمامة من جمعها. \n
- وضع علامة على الخلية بأنها "فارغة" - علامة خاصة
emptyRestأوemptyOne. \n - تقليل العداد -
m.count--. \n
كل هذا يحدث بمتوسط O(1)، ولكن مع مراعاة التصادمات، قد يكون O(n) في أسوأ الحالات.
\n\nمثال كود: delete قيد التنفيذ
\n\npackage main\n\nimport "fmt"\n\nfunc main() {\n m := map[string]int{\n "apple": 5,\n "banana": 3,\n "cherry": 7,\n }\n\n fmt.Println("قبل الحذف:", m)\n delete(m, "banana")\n fmt.Println("بعد الحذف:", m)\n\n // لنحاول حذف مفتاح غير موجود\n delete(m, "grape") // لن يحدث شيء، لا يوجد خطأ\n fmt.Println("بعد حذف مفتاح غير موجود:", m)\n}\n\nلاحظ: delete key in map لمفتاح غير موجود هي عملية آمنة. إنها ببساطة لا تفعل شيئًا. هذا أحد المفاهيم الخاطئة الشائعة بين المبتدئين.
\n\nماذا يحدث للذاكرة؟ الخرافات والواقع
\n\nيخشى العديد من المطورين من أن تبقى "فجوات" في map بعد delete تؤدي إلى تسرب الذاكرة. دعنا نوضح الأمر.
الذاكرة الخاصة بالمفاتيح والقيم
\n\nعندما تحذف مفتاحًا، يقوم Go بمسح الذاكرة الخاصة بهذا العنصر. إذا كان المفتاح أو القيمة عبارة عن مؤشرات أو تحتوي على مؤشرات (سلاسل نصية، شرائح، maps)، فسيتمكن جامع القمامة من جمعها. الخلية نفسها في الدلو تبقى، ولكن يتم وضع علامة عليها بأنها فارغة.
\n\nهذا يعني أن map لا يتقلص في الحجم