إنشاء خريطة واستخدامها
يُكتب نوع الخريطة (map) بالشكل map[KeyType]ValueType. أنشئ واحدة بحرفية أو بـ make، ثم اقرأ واكتب واحذف بالمفتاح.
المخرجات:
31
3
map[bob:26 cy:40]
2 0
تظهر هنا ميزتان مريحتان. قراءة مفتاح مفقود تعيد القيمة الصفرية لنوع القيمة (counts['z'] تساوي 0)، وهذا يجعل العدّ بـ m[k]++ يعمل دون أي تجهيز. وتطبع fmt الخرائط بمفاتيح مرتّبة، وهذا مفيد لتتبّع الأخطاء لكنه لا يقول شيئًا عن ترتيب المرور.
تأخذ make(map[K]V, n) تلميحًا اختياريًا للحجم. تحجز مسبقًا مكانًا لنحو n مدخلًا؛ وبخلاف الشرائح لا سعة للخريطة يمكنك قراءتها.
التحقّق من وجود مفتاح: comma-ok
لأن المفتاح المفقود يُقرأ كقيمة صفرية، لا تستطيع m[k] == 0 التمييز بين "غائب" و"مخزّن كـ 0". استخدم الصيغة ذات القيمتين:
الشكل if v, ok := m[k]; ok { ... } يحصر v وok داخل if. وهو من أشيع الأسطر في شيفرات Go.
حذف المدخلات
تزيل delete(m, key) المدخل. حذف مفتاح غير موجود لا يفعل شيئًا، وكذلك الحذف من خريطة nil. لإفراغ خريطة كاملة أضافت Go 1.21 الدالة clear(m)، التي تحتفظ بالخريطة المحجوزة لإعادة استخدامها.
حذف المدخلات أثناء range على الخريطة نفسها مسموح وآمن. المدخل المحذوف قبل أن تصل إليه الحلقة لن يظهر.
المرور على العناصر: الترتيب عشوائي
تزور for k, v := range m كل مدخل مرة واحدة، بترتيب غير محدّد. يجعل وقت التشغيل نقطة البداية عشوائية عمدًا، فكثيرًا ما تختلف حلقتان على الخريطة نفسها في البرنامج نفسه. شغّل هذا بضع مرات:
أي شيفرة تعتمد مخرجاتها على ترتيب الخريطة خطأ ينتظر تشغيلًا مختلفًا. الاختبارات التي تقارن مرورًا مطبوعًا على خريطة هي المثال الكلاسيكي.
المفاتيح المرتّبة
لزيارة خريطة بترتيب المفاتيح، احصل على المفاتيح ورتّبها وافهرس الخريطة بها. جعلت Go 1.23 هذا سطرًا واحدًا بمكرِّرات الحزمتين maps وslices:
في Go 1.22 وما قبله لم تكن maps.Keys موجودة في المكتبة القياسية. والمكافئ حلقة:
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
دوال مساعدة أخرى في maps: maps.Values وmaps.Clone (نسخة سطحية) وmaps.Equal وmaps.Copy(dst, src) وmaps.DeleteFunc.
أنواع المفاتيح الصالحة
يجب أن تكون المفاتيح قابلة للمقارنة بـ ==: الأرقام والنصوص والقيم المنطقية والمؤشرات والقنوات ومصفوفات الأنواع القابلة للمقارنة والبنى التي كل حقولها قابلة للمقارنة وقيم الواجهات. لا يمكن أن تكون الشرائح والخرائط والدوال مفاتيح.
map[[]int]bool{} // compile error: invalid map key type []int
map[[2]int]bool{} // fine: arrays are comparable
map[struct{ X, Y int }]string{} // fine: a struct key for a grid position
المفتاح من نوع بنية هو الطريقة المعتادة للفهرسة بعدة قيم معًا، بدل ضمّ النصوص.
مفاتيح الواجهات تُترجم حتى عندما يكون النوع الديناميكي غير قابل للمقارنة، ثم تسبّب panic وقت التشغيل: تخزين []int في map[any]int يفشل برسالة runtime error: hash of unhashable type []int.
مفاتيح الأعداد العشرية تعمل، لكن NaN لا يساوي نفسه، فيمكن إدراج مفتاح NaN مرارًا دون أن يُقرأ أبدًا. تجنّب المفاتيح العشرية.
خريطة من البنى
يمكن للخريطة أن تحمل بنى، لكنك لا تستطيع الإسناد إلى حقل في بنية مخزّنة في خريطة، لأن قيم الخرائط لا عنوان لها.
اختر القيم عندما تكون المدخلات صغيرة وتُستبدل كاملة. واختر المؤشرات عندما تحدّث الحقول كثيرًا أو تشارك السجل نفسه من عدة أماكن. مع المؤشرات يعيد المفتاح المفقود nil، فتسبّب ptrs["nope"].Score حالة panic.
خرائط الشرائح تعمل بالطريقة نفسها مع الإلحاق: groups[k] = append(groups[k], v) لا تحتاج تهيئة، لأن المفتاح المفقود يعطي شريحة nil وappend تتعامل مع nil.
الخرائط شبيهة بالمراجع
قيمة الخريطة تشير إلى بيانات مشتركة. إسناد خريطة أو تمريرها إلى دالة لا ينسخ المدخلات: يرى المتغيّران الخريطة نفسها.
لهذا تستطيع الدالة ملء خريطة دون إعادتها، بخلاف شريحة تلحق بها.
panic الخريطة nil
القيمة الصفرية للخريطة هي nil. الخريطة nil تُقرأ كخريطة فارغة، لكن الكتابة فيها تسبّب panic.
المخرجات:
0 0
recovered: assignment to entry in nil map
حالة البنية هي التي تلدغ عمليًا. هيّئ حقول الخرائط في دالة بناء (func NewCache() *Cache { return &Cache{data: map[string]string{}} }) أو بكسل قبل أول كتابة.
الوصول المتزامن
الخرائط غير آمنة للاستخدام المتزامن. إذا كتبت goroutine بينما تقرأ أخرى أو تكتب، قد يوقف وقت التشغيل البرنامج برسالة fatal error: concurrent map writes (أو concurrent map read and map write). هذا خطأ قاتل لا panic، فلا يستطيع recover التقاطه.
احمِ الخريطة بـ mutex:
يطبع هذا دائمًا 50 50. استخدم sync.RWMutex عندما تفوق القراءات الكتابات بكثير. توجد sync.Map لحالتين ضيقتين (مفاتيح تُكتب مرة وتُقرأ مرات كثيرة، أو goroutines تعمل على مفاتيح منفصلة)؛ ولكل ما عدا ذلك فإن mutex مع خريطة عادية أبسط وأسرع عادة. المزيد في صفحة mutex.
مرجع سريع
| العملية | الشيفرة |
|---|---|
| الإنشاء | m := map[string]int{} أو make(map[string]int) |
| الإدراج أو التحديث | m[k] = v |
| القراءة (صفر عند الغياب) | v := m[k] |
| فحص الوجود | v, ok := m[k] |
| الحذف | delete(m, k) |
| إزالة الكل | clear(m) (Go 1.21) |
| الحجم | len(m) |
| المفاتيح المرتّبة | slices.Sorted(maps.Keys(m)) (Go 1.23) |
| النسخ | maps.Clone(m) |
| المقارنة | maps.Equal(a, b) |
الخريطة ذات القيم struct{} هي أيضًا نوع المجموعة في Go؛ راجع المجموعات.
أخطاء شائعة
- الكتابة في خريطة nil. أنشئها دائمًا بـ
make، بما في ذلك حقول الخرائط في البنى. - الاعتماد على ترتيب المرور. رتّب المفاتيح.
- استخدام
m[k] != 0لفحص الوجود. استخدم comma-ok. - تعديل حقل بنية عبر
m[k].Field. انسخها خارجًا ثم أعد كتابتها، أو خزّن مؤشرات. - مشاركة خريطة بين goroutines دون قفل. لا يمكن التعافي من الانهيار.
الأسئلة الشائعة
كيف تتحقّق من وجود مفتاح في خريطة في Go؟
استخدم صيغة البحث ذات القيمتين: v, ok := m[key]. تكون ok مساوية لـ true عند وجود المفتاح وfalse عند غيابه، وفي هذه الحالة تكون v القيمة الصفرية. قراءة m[key] وحدها لا تميّز المفتاح المفقود من المفتاح المخزّن بالقيمة الصفرية.
لماذا يكون ترتيب المرور على الخريطة في Go عشوائيًا؟
لا تحدّد اللغة ترتيبًا، ووقت التشغيل يبدأ كل range عمدًا من موضع عشوائي كي لا تعتمد البرامج على ترتيب ما. للمرور بترتيب المفاتيح اجمعها ورتّبها: for _, k := range slices.Sorted(maps.Keys(m)) (Go 1.23).
كيف أحصل على كل مفاتيح خريطة في Go؟
منذ Go 1.23 تعيد maps.Keys(m) مكرِّرًا؛ حوّله إلى شريحة بـ slices.Collect(maps.Keys(m))، أو إلى شريحة مرتّبة بـ slices.Sorted(maps.Keys(m)). قبل 1.23 مرّ بحلقة for k := range m وألحق كل مفتاح بشريحة.
لماذا تسبّب الكتابة في خريطة panic برسالة "assignment to entry in nil map"؟
عُرّف متغيّر الخريطة لكنه لم يُنشأ قط: var m map[string]int قيمتها nil. قراءة الخريطة nil تعيد القيم الصفرية، لكن الكتابة تسبّب panic. أنشئها أولًا بـ m = make(map[string]int) أو بحرفية m := map[string]int{}. وحقل الخريطة داخل بنية يحتاج التهيئة نفسها.
هل الخرائط في Go آمنة للاستخدام المتزامن؟
لا. الكتابات المتزامنة، أو كتابة متزامنة مع قراءات، قد تُسقط البرنامج برسالة fatal error: concurrent map writes، التي لا يستطيع recover التقاطها. احمِ الخريطة بـ sync.Mutex أو sync.RWMutex، أو استخدم sync.Map للحالات المحدّدة التي صُمّمت لها.