Go: Go Map والهياكل

آخر تحديث: 2026-08-26

تُعد «الخرائط» و«الهياكل» الركيزتين الأساسيتين لنمذجة البيانات في لغة Go — حيث تتولى «الخرائط» معالجة الاستعلامات الديناميكية، بينما تصف «الهياكل» البنى الثابتة — ويمكنهما معًا وصف 90% من كائنات الأعمال.

تتيح الخرائط (Maps) إجراء عمليات البحث عن القيم باستخدام المفاتيح بزمن O(1)، بينما تقوم البنيات (structs) بتجميع البيانات باستخدام الحقول. في هذا الدرس، ستتقن جميع المفاهيم الأساسية لنمذجة البيانات في لغة Go وستتمكن من استخدامها لبناء نظام مخزون متكامل للتجارة الإلكترونية.

1. ستتعلم



2. القصة الحقيقية لمهندس في مجال التجارة الإلكترونية

(1) المشكلة: عمليات البحث في الشرائح بطيئة للغاية

يعمل بوب كمهندس خلفي في إحدى منصات التجارة الإلكترونية. ومع انطلاق حملة التخفيضات المسبقة لـ«Double 11»، أصبحت واجهة برمجة التطبيقات (API) الخاصة بالاستعلام عن المخزون بطيئة بشكل لا يُصدق:

"لدينا 100,000 وحدة تخزين (SKU) في المخزون، مخزنة باستخدام شريحة []Product، وكل استعلام يتطلب مسحًا خطيًّا. مع زيادة معدل الاستعلامات في الثانية (QPS)، يبلغ متوسط وقت الاستجابة 800 مللي ثانية مع استخدام بنسبة 100% لوحدة المعالجة المركزية (CPU). وقد منحني مديري ثلاثة أيام لتحسين هذا الأداء بحيث يصبح أقل من 100 مللي ثانية."

فتح الكود الذي كان قد كتبه:

GO
// Version 1: Store all products using a slice
var products []Product

func findProduct(sku string) *Product {
    for _, p := range products {  // O(n) linear scan
        if p.SKU == sku {
            return &p
        }
    }
    return nil
}

100,000 منتج × 1,000 عملية في الثانية = 100 مليون عملية مسح في الثانية؛ ولا مفر من وصول معدل استخدام وحدة المعالجة المركزية (CPU) إلى 100%.

(2) الحل بلغة Go: استخدام خريطة (map) لإجراء عمليات البحث بزمن O(1)

GO
// inventory.go
package main

import "fmt"

type Product struct {
    SKU      string  // Product unique identifier
    Name     string  // Product name
    Price    float64 // Price
    Stock    int     // Stock count
    Category string  // Classification
}

// Using map to create a hash table
type Inventory struct {
    products map[string]Product  // SKU -> Product
}

func NewInventory() *Inventory {
    return &Inventory{products: make(map[string]Product)}
}

// O(1) query
func (inv *Inventory) Find(sku string) (Product, bool) {
    p, ok := inv.products[sku]
    return p, ok
}

// O(1) update
func (inv *Inventory) UpdateStock(sku string, delta int) error {
    p, ok := inv.products[sku]
    if !ok {
        return fmt.Errorf("SKU %s not found", sku)
    }
    p.Stock += delta
    inv.products[sku] = p
    return nil
}

func main() {
    inv := NewInventory()

    // Bulk import of 100,000 SKUs
    for i := 0; i < 100000; i++ {
        sku := fmt.Sprintf("SKU-%05d", i)
        inv.products[sku] = Product{
            SKU:      sku,
            Name:     fmt.Sprintf("Product-%d", i),
            Price:    99.99,
            Stock:    100,
            Category: "electronics",
        }
    }

    // Query performance comparison
    p, ok := inv.Find("SKU-50000")
    if ok {
        fmt.Printf("Found: %s, price=%.2f\n", p.Name, p.Price)
    }

    // Update inventory
    inv.UpdateStock("SKU-50000", -5)
}

الناتج:

TEXT 📖 للعرض فقط
Found: Product-50000, price=99.99

(3) الأداء: مقارنة أداء الاستعلامات باستخدام «slice» و«map»

حجم البيانات المسح الخطي في الشريحة البحث عن التجزئة في الخريطة الفارق في الأداء
100 SKU 50 نانو ثانية 50 نانو ثانية مكافئ
1,000 SKU 500 نانو ثانية 50 نانو ثانية 10x
10,000 SKU 5 ميكروثانية 50 نانوثانية 100x
100,000 SKU 50 ميكروثانية 50 نانوثانية 1,000x
💡 نصيحة: تعقيد O(1) للخريطة هو التعقيد المتوسط استنادًا إلى جدول التجزئة — وفي أسوأ الحالات (تضارب التجزئة)، ينخفض إلى O(n). تعمل لغة Go على تقليل احتمالية حدوث ذلك إلى أدنى حد من خلال دالة تجزئة وآلية لتغيير الحجم مصممتين بعناية.



3. أساسيات الخرائط

(1) ثلاث طرق لإنشاء خريطة

GO
package main

import "fmt"

func main() {
    // Method 1: make (Recommended)
    m1 := make(map[string]int)  // empty map, writable

    // Method 2: make + pre-allocated capacity
    m2 := make(map[string]int, 100)  // pre-allocate 100 capacity, reduce resizing

    // Method 3: Literal Initialization
    m3 := map[string]int{
        "Alice": 28,
        "Bob":   32,
    }

    // Method 4: nil map (read-only, cannot be written to)
    var m4 map[string]int  // == nil, cannot do m4["a"] = 1
    _ = m4
}

(2) خريطة CRUD

GO
package main

import "fmt"

func main() {
    ages := make(map[string]int)

    // Create
    ages["Alice"] = 28
    ages["Bob"] = 32

    // Read
    fmt.Println(ages["Alice"])  // 28

    // Update
    ages["Alice"] = 29

    // Delete
    delete(ages, "Bob")

    // Length
    fmt.Printf("len=%d\n", len(ages))

    fmt.Println(ages)  // map[Alice:29]
}

الناتج:

TEXT 📖 للعرض فقط
28
len=1
map[Alice:29]

▶ مثال: تكرار الخريطة (الترتيب عشوائي)

GO
package main

import "fmt"

func main() {
    ages := map[string]int{"Alice": 28, "Bob": 32, "Charlie": 45}

    // for range: key + value
    for name, age := range ages {
        fmt.Printf("%s is %d years old\n", name, age)
    }

    // Only the key
    for name := range ages {
        fmt.Printf("name: %s\n", name)
    }

    // Only the value (use _ to ignore the key)
    for _, age := range ages {
        fmt.Printf("age: %d\n", age)
    }
}
▶ جرّب الكود

الناتج (بترتيب عشوائي):

TEXT 📖 للعرض فقط
Alice is 28 years old
Charlie is 45 years old
Bob is 32 years old
name: Bob
...
🔥 خطأ شائع: يتم ترتيب عناصر خريطة Go عشوائيًا عن قصد — وذلك لمنع المبرمجين من الاعتماد على ترتيب معين. إذا كنت بحاجة إلى ترتيب ثابت، فقم بفرز المفاتيح أولاً.



4. قواعد استخدام الفاصلة (نقطة أساسية)

(1) التمييز بين «القيمة الصفرية» و«غير موجود»

GO
package main

import "fmt"

func main() {
    ages := map[string]int{"Alice": 28}

    // Incorrect approach: Cannot distinguish between "key does not exist" and "value = 0"
    age := ages["Bob"]
    fmt.Printf("Bob's age: %d\n", age)  // 0 (but is Bob really 0 years old?)

    // Correct approach: comma-ok
    age, ok := ages["Bob"]
    if ok {
        fmt.Printf("Bob's age: %d\n", age)
    } else {
        fmt.Println("Bob not found")
    }

    // Just check if it exists: discard the value
    _, exists := ages["Bob"]
    fmt.Printf("Bob exists: %v\n", exists)
}

الناتج:

TEXT 📖 للعرض فقط
Bob's age: 0
Bob not found
Bob exists: false

(2) استخدام الفاصلة في الممارسة العملية: التخزين المؤقت للاستعلامات

GO
package main

import "fmt"

// Cache: key → value
var cache = make(map[string]string)

func getCached(key string) (string, bool) {
    val, ok := cache[key]
    return val, ok
}

func main() {
    // Set cache
    cache["user:1"] = "Alice"
    cache["user:2"] = "Bob"

    // Query cache
    if val, ok := getCached("user:1"); ok {
        fmt.Printf("Hit: %s\n", val)
    } else {
        fmt.Println("Miss")
    }

    if _, ok := getCached("user:999"); !ok {
        fmt.Println("user:999 not in cache, fetching from DB...")
    }
}

الناتج:

TEXT 📖 للعرض فقط
Hit: Alice
user:999 not in cache, fetching from DB...

(3) النمط العام لـ «comma-ok»

السياق الصيغة المعنى
البحث في الخريطة v, ok := m[key] هل المفتاح موجود؟
تأكيد النوع v, ok := x.(T) مطابقة النوع؟
الاستقبال من القناة v, ok := <-ch هل القناة مغلقة؟


5. أساسيات الهياكل (Structs)

(1) تعريف الهياكل وتهيئتها

GO
package main

import "fmt"

// define struct
type User struct {
    Name string
    Age  int
    City string
}

func main() {
    // Method 1: By field order (not recommended; poor readability)
    u1 := User{"Alice", 28, "Shanghai"}

    // Method 2: Initializing Field Names (Recommended)
    u2 := User{
        Name: "Bob",
        Age:  32,
        City: "Beijing",
    }

    // Method 3: Partial Initialization (with the remainder set to zero)
    u3 := User{Name: "Charlie"}  // Age=0, City=""

    // Method 4: new() returns a pointer
    u4 := new(User)
    u4.Name = "Dave"

    fmt.Println(u1, u2, u3, u4)
}

الناتج:

TEXT 📖 للعرض فقط
{Alice 28 Shanghai} {Bob 32 Beijing} {Charlie  } &{Dave 0 }

(2) الوصول إلى الحقول وتعديلها

GO
package main

import "fmt"

type User struct {
    Name string
    Age  int
}

func main() {
    u := User{Name: "Alice", Age: 28}

    // Read field
    fmt.Println(u.Name)  // Alice

    // Write to a field
    u.Age = 29

    // Pointer access (automatic dereferencing)
    p := &u
    fmt.Println(p.Name)  // Alice (equivalent to (*p).Name)
    p.Age = 30  // automatic dereferencing
}

▶ مثال: نسخ البنية مقابل المؤشرات

GO
package main

import "fmt"

type Counter struct {
    Value int
}

// Pass-by-value: Copy the entire struct
func incrementByValue(c Counter) {
    c.Value++  // modifies the copy
    fmt.Printf("Inside function: %d\n", c.Value)
}

// Pointer passing: pass by reference
func incrementByPointer(c *Counter) {
    c.Value++  // modifies the original
    fmt.Printf("Inside function: %d\n", c.Value)
}

func main() {
    c := Counter{Value: 10}

    incrementByValue(c)
    fmt.Printf("After value passing: %d\n", c.Value)  // 10 (unchanged)

    incrementByPointer(&c)
    fmt.Printf("After pointer passing: %d\n", c.Value)  // 11 (modified)
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
Inside function: 11
After value passing: 10
Inside function: 11
After pointer passing: 11
🔥 خطأ شائع: البنية (struct) هي نوع قيمة، لذا فإن تمريرها كمعلمة يؤدي إلى نسخ البنية بأكملها. في حالة البنى الكبيرة أو عندما تحتاج إلى تعديل الكائن الأصلي، يجب عليك استخدام مؤشر.



6. علامة البنية (مفتاح التسلسل إلى JSON)

(1) صيغة علامة البنية

GO
type User struct {
    Name     string `json:"name" db:"user_name"`
    Age      int    `json:"age" validate:"min=0,max=150"`
    Email    string `json:"email,omitempty"`
    Password string `json:"-"`  // - means JSON ignores this field
}

(2) التسلسل العملي لـ JSON

GO
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name     string `json:"name"`
    Age      int    `json:"age"`
    Email    string `json:"email,omitempty"`
    Password string `json:"-"`  // password not serialized
}

func main() {
    u := User{
        Name:     "Alice",
        Age:      28,
        Email:    "alice@example.com",
        Password: "secret123",
    }

    // Serialization: struct → JSON
    data, _ := json.Marshal(u)
    fmt.Printf("JSON: %s\n", data)

    // Deserialization: JSON → struct
    jsonStr := `{"name":"Bob","age":32,"email":"bob@example.com"}`
    var u2 User
    json.Unmarshal([]byte(jsonStr), &u2)
    fmt.Printf("Deserialization: %+v\n", u2)
}

الناتج:

TEXT 📖 للعرض فقط
JSON: {"name":"Alice","age":28,"email":"alice@example.com"}
Deserialization: {Name:Bob Age:32 Email:bob@example.com Password:}

(3) مكتبات علامات البنية الشائعة الاستخدام

المكتبة اسم العلامة الغرض
encoding/json json:"name" اسم حقل JSON
gorm gorm:"primaryKey" قيود حقول ORM
أداة التحقق من الصحة validate:"required" التحقق من صحة الحقول
yaml yaml:"name" تسلسل YAML
💡 نصيحة: سيتناول الدرس رقم 10، «file-json»، جميع تفاصيل تسلسل JSON بتعمق.



7. الهياكل المتداخلة

(1) الهياكل المتداخلة (التركيب بدلاً من الوراثة)

لا توجد ميزة «الوراثة» في لغة Go؛ فهي تستخدم الهياكل المتداخلة لتحقيق التركيب:

GO
package main

import "fmt"

type Address struct {
    City    string
    Country string
}

type User struct {
    Name    string
    Age     int
    Address Address  // nested Address
}

func main() {
    u := User{
        Name: "Alice",
        Age:  28,
        Address: Address{
            City:    "Shanghai",
            Country: "China",
        },
    }

    // Accessing nested fields
    fmt.Println(u.Address.City)     // Shanghai
    fmt.Println(u.Address.Country)  // China
}

(2) التداخل المجهول (ترقية الحقول)

GO
package main

import "fmt"

type Address struct {
    City    string
    Country string
}

// Anonymous nesting: fields are "promoted"
type User struct {
    Name string
    Age  int
    Address  // equivalent to Address Address, but without the field name
}

func main() {
    u := User{
        Name: "Alice",
        Age:  28,
        Address: Address{
            City:    "Shanghai",
            Country: "China",
        },
    }

    // Field promotion: direct access, without u.Address.City
    fmt.Println(u.City)     // Shanghai
    fmt.Println(u.Country)  // China

    // You can also use the full path
    fmt.Println(u.Address.City)
}

▶ مثال: العلامات المتداخلة + JSON في التطبيق العملي

GO
package main

import (
    "encoding/json"
    "fmt"
)

type Address struct {
    City    string `json:"city"`
    Country string `json:"country"`
}

type User struct {
    Name    string  `json:"name"`
    Age     int     `json:"age"`
    Email   string  `json:"email,omitempty"`
    Address Address `json:"address"`
}

func main() {
    u := User{
        Name:  "Alice",
        Age:   28,
        Email: "alice@example.com",
        Address: Address{
            City:    "Shanghai",
            Country: "China",
        },
    }

    data, _ := json.MarshalIndent(u, "", "  ")
    fmt.Println(string(data))
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
{
  "name": "Alice",
  "age": 28,
  "email": "alice@example.com",
  "address": {
    "city": "Shanghai",
    "country": "China"
  }
}


8. الاختيار بين map و slice

(1) جدول مرجعي لاختيار النموذج

البعد شريحة []T خريطة map[K]V
طريقة البحث المسح الخطي O(n) التجزئة O(1)
هل تم طلبه؟ ✅ تم طلبه ❌ لم يتم طلبه
استخدام الذاكرة مضغوط (24 + طول*الحجم) أقل ضغطًا (جدول التجزئة + الحجرة)
حذف عنصر يتطلب نقلًا يدويًّا delete(m, k) O(1)
السيناريوهات النموذجية القوائم/قوائم الانتظار/المكدسات/الفرز القواميس/ذاكرات التخزين المؤقت/الفهارس

(2) شجرة قرار اختيار النموذج

100%
graph TB
    A[Need to store multiple elements] --> B{Need to look up by key?}
    B -->|Yes| C[Use map<br/>O(1) lookup]
    B -->|No| D{Need to maintain order?}
    D -->|Yes| E[Use slice]
    D -->|No| F{Element count < 100?}
    F -->|Yes| G[Either slice or map works]
    F -->|No| C

(3) مثال من الواقع: إدارة درجات الطلاب

GO
package main

import "fmt"

// Store student grades in a map (look up by student ID)
type GradeBook struct {
    scores map[string]int  // Student ID → Score
}

func (gb *GradeBook) Set(id string, score int) {
    gb.scores[id] = score
}

func (gb *GradeBook) Get(id string) (int, bool) {
    score, ok := gb.scores[id]
    return score, ok
}

// Use a slice to store a list of scores (for sorting or calculating the average)
func average(scores []int) float64 {
    if len(scores) == 0 {
        return 0
    }
    sum := 0
    for _, s := range scores {
        sum += s
    }
    return float64(sum) / float64(len(scores))
}

func main() {
    gb := &GradeBook{scores: make(map[string]int)}
    gb.Set("S001", 95)
    gb.Set("S002", 82)
    gb.Set("S003", 67)

    if s, ok := gb.Get("S001"); ok {
        fmt.Printf("S001: %d\n", s)
    }

    // Collect all scores and calculate the average
    allScores := []int{}
    for _, s := range gb.scores {
        allScores = append(allScores, s)
    }
    fmt.Printf("Average Score: %.2f\n", average(allScores))
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
S001: 95
Average Score: 81.33
💡 نصيحة: في المشاريع العملية، من الشائع استخدام كل من map و slice معًا — حيث تُستخدم map للاستعلام، بينما تُستخدم slice للفرز أو التجميع.



9. مثال كامل: نظام جرد وحدات التخزين (SKU) للتجارة الإلكترونية

اجمع بين جميع ميزات الخرائط والهياكل (structs) لإنشاء نظام كامل للاستعلام عن مخزون التجارة الإلكترونية:

GO
// inventory_system.go
package main

import (
    "encoding/json"
    "fmt"
    "sort"
)

// Product
type Product struct {
    SKU      string   `json:"sku"`
    Name     string   `json:"name"`
    Price    float64  `json:"price"`
    Stock    int      `json:"stock"`
    Category string   `json:"category"`
    Tags     []string `json:"tags,omitempty"`  // Tags: new/hot/discount
}

// Inventory System
type Inventory struct {
    products map[string]Product  // SKU → Product
}

func NewInventory() *Inventory {
    return &Inventory{products: make(map[string]Product)}
}

// Add product
func (inv *Inventory) Add(p Product) {
    inv.products[p.SKU] = p
}

// Query (comma-ok)
func (inv *Inventory) Find(sku string) (Product, bool) {
    p, ok := inv.products[sku]
    return p, ok
}

// Update stock
func (inv *Inventory) UpdateStock(sku string, delta int) error {
    p, ok := inv.products[sku]
    if !ok {
        return fmt.Errorf("SKU %s not found", sku)
    }
    newStock := p.Stock + delta
    if newStock < 0 {
        return fmt.Errorf("insufficient stock for %s: have %d, need %d",
            sku, p.Stock, -delta)
    }
    p.Stock = newStock
    inv.products[sku] = p
    return nil
}

// Sort by price (unordered map → convert to slice)
func (inv *Inventory) ListByPrice() []Product {
    list := make([]Product, 0, len(inv.products))
    for _, p := range inv.products {
        list = append(list, p)
    }
    sort.Slice(list, func(i, j int) bool {
        return list[i].Price < list[j].Price
    })
    return list
}

// Filter by category
func (inv *Inventory) FindByCategory(category string) []Product {
    var result []Product
    for _, p := range inv.products {
        if p.Category == category {
            result = append(result, p)
        }
    }
    return result
}

func main() {
    inv := NewInventory()

    // Initialize 100 SKUs
    categories := []string{"electronics", "clothing", "food"}
    for i := 0; i < 100; i++ {
        inv.Add(Product{
            SKU:      fmt.Sprintf("SKU-%05d", i),
            Name:     fmt.Sprintf("Product-%d", i),
            Price:    float64(i%50 + 10),
            Stock:    100 - i%30,
            Category: categories[i%3],
        })
    }

    // 1. Look up a single SKU
    if p, ok := inv.Find("SKU-0050"); ok {
        fmt.Printf("Found: %s, price=%.2f, stock=%d\n", p.Name, p.Price, p.Stock)
    }

    // 2. Update inventory
    if err := inv.UpdateStock("SKU-0050", -10); err == nil {
        fmt.Println("Stock updated successfully")
    }

    // 3. Filter by category
    electronics := inv.FindByCategory("electronics")
    fmt.Printf("\nElectronics products: %d\n", len(electronics))

    // 4. Sort by price (top 3)
    sorted := inv.ListByPrice()
    fmt.Println("\nTop 3 cheapest:")
    for i, p := range sorted[:3] {
        fmt.Printf("  %d. %s: %.2f\n", i+1, p.Name, p.Price)
    }

    // 5. JSON Serialization (API Response)
    if p, ok := inv.Find("SKU-0050"); ok {
        data, _ := json.MarshalIndent(p, "", "  ")
        fmt.Printf("\nJSON output:\n%s\n", data)
    }
}

النتيجة المتوقعة:

TEXT 📖 للعرض فقط
Found: Product-50, price=10.00, stock=70
Stock updated successfully

Electronics products: 34

Top 3 cheapest:
  1. Product-0: 10.00
  2. Product-30: 10.00
  3. Product-60: 10.00

JSON output:
{
  "sku": "SKU-0050",
  "name": "Product-50",
  "price": 10,
  "stock": 60,
  "category": "food"
}
🔥 خطأ شائع: في طريقة ListByPrice، تستخدم طريقة sort.Slice دالة مغلقة كدالة مقارنة. وتقوم الدالة المغلقة بالتقاط المتغير list واستدعائه أثناء كل عملية مقارنة.



10. مجموعات أمثلة إضافية

▶ مثال: مقارنة الأداء بين التهيئة باستخدام قيمة خريطة حرفية واستخدام الدالة make

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    // Literal initialization
    start1 := time.Now()
    m1 := map[string]int{
        "a": 1, "b": 2, "c": 3, "d": 4, "e": 5,
        "f": 6, "g": 7, "h": 8, "i": 9, "j": 10,
    }
    fmt.Printf("Literals: %v, %v\n", len(m1), time.Since(start1))

    // make pre-allocation
    start2 := time.Now()
    m2 := make(map[string]int, 10)
    m2["a"] = 1
    m2["b"] = 2
    m2["c"] = 3
    m2["d"] = 4
    m2["e"] = 5
    m2["f"] = 6
    m2["g"] = 7
    m2["h"] = 8
    m2["i"] = 9
    m2["j"] = 10
    fmt.Printf("make: %v, %v\n", len(m2), time.Since(start2))
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
Literals: 10, 1.2µs
make: 10, 850ns

▶ مثال: الطرق التي تقبل قيم «struct» مقابل الطرق التي تقبل المؤشرات

GO
package main

import "fmt"

type Counter struct {
    Value int
}

// Value receiver: operates on a copy; does not affect the original
func (c Counter) IncrementValue() Counter {
    c.Value++
    return c
}

// Pointer receiver: directly modifies the original
func (c *Counter) IncrementPointer() {
    c.Value++
}

func main() {
    c := Counter{Value: 10}

    // Value receiver: must use the return value
    c = c.IncrementValue()
    fmt.Printf("After value receiver: %d\n", c.Value)  // 11

    // Pointer receiver: direct modification
    c.IncrementPointer()
    fmt.Printf("After pointer receiver: %d\n", c.Value)  // 12
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
After value receiver: 11
After pointer receiver: 12

▶ مثال: توضيح مخاطر التزامن في دالة map

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    m := make(map[string]int)

    // Writing goroutine
    go func() {
        for i := 0; i < 1000; i++ {
            m["key"] = i  // write operation
        }
    }()

    // Reading goroutine
    go func() {
        for i := 0; i < 1000; i++ {
            _ = m["key"]  // read operation
        }
    }()

    time.Sleep(100 * time.Millisecond)
    fmt.Println("Concurrent reads and writes may cause a panic (fatal error: concurrent map read and map write)")
}
▶ جرّب الكود

الناتج (قد يتسبب في حدوث حالة ذعر أثناء وقت التشغيل):

TEXT 📖 للعرض فقط
Concurrent reads and writes may cause a panic (fatal error: concurrent map read and map write)
🔥 خطأ شائع: هذا خطأ شائع في التزامن في لغة Go. يجب حماية الخرائط المتزامنة باستخدام sync.Map أو sync.Mutex؛ وقد تم تناول هذا الموضوع بالتفصيل في الدرس 16.



❓ أسئلة شائعة

س ماذا يحدث إذا حاولت الوصول إلى مفتاح غير موجود في خريطة؟
ج تُرجع القيمة صفر. ومع ذلك، قد تكون القيمة صفر قيمة صالحة (على سبيل المثال، age=0)، لذا يجب عليك استخدام "comma-ok" للتمييز بين "غير موجود" و"القيمة صفر".
س هل «map» نوع مرجعي؟
ج نعم. متغير «map» هو مؤشر إلى جدول هاش؛ حيث تشترك عمليات التعيين وتمرير المعلمات في نفس جدول الهاش الأساسي. وهو مشابه لـ«slice» ولكنه أكثر جذرية — ففي حين أن «slice» يمكن أن تؤدي إلى تغيير الحجم بشكل منفصل عبر عملية «append»، فإن «map» تفتقر إلى آلية العزل هذه.
س هل يمكن مقارنة البنيات (structs)؟
ج فقط إذا كانت جميع الحقول قابلة للمقارنة. لا يمكن مقارنة البنيات التي تحتوي على شرائح (slices) أو خرائط (maps) أو دالات (func) (خطأ في الترجمة). استخدم reflect.DeepEqual() لإجراء مقارنة عميقة.
س متى يجب استخدام البنيات المتداخلة، ومتى يجب استخدام المؤشرات؟
ج (1) إذا كنت بحاجة إلى تعديل الكائن الأصلي → استخدم المؤشرات المتداخلة؛ (2) إذا كانت البنية كبيرة الحجم → استخدم المؤشرات المتداخلة (لتجنب النسخ)؛ (3) إذا كان التعدد الشكلي مطلوبًا (لتنفيذ واجهة) → استخدم المؤشرات؛ (4) دلالات القيمة، الكائنات غير القابلة للتغيير → استخدم تداخل القيم.
س ما هي القيود المفروضة على مفاتيح الخرائط؟
ج يجب أن تكون المفاتيح من أنواع قابلة للمقارنة — bool أو int أو float أو string — أو من البنيات التي تحتوي على هذه الأنواع. ولا يجوز أن تكون شرائح أو خرائط أو func.
س ماذا يحدث إذا كان تنسيق علامة البنية غير صحيح؟
ج سيتم ترجمة الكود، لكن المكتبات مثل مكتبة تسلسل JSON لن تتمكن من التعرف عليه. ومن الأخطاء الشائعة: الأخطاء الإملائية في اسم العلامة (مثل وجود مسافة زائدة في json:"name") أو استخدام نوع خاطئ من علامات الاقتباس (يجب استخدام علامات الاقتباس المزدوجة).
س لماذا يكون التكرار على الخريطة عشوائيًا؟
ج هذا أمر مقصود في لغة Go — لمنع المبرمجين من الاعتماد على ترتيب معين. إذا كنت بحاجة إلى ترتيب ثابت، فاستخدم أولاً شريحة (slice) لتجميع جميع المفاتيح، ثم قم بفرز المفاتيح، وأخيرًا ابحث في الخريطة باستخدام المفاتيح المفروزة.
س هل الخريطة آمنة من حيث التداخل بين الخيوط؟
ج لا! إذا قامت عدة goroutines بالقراءة من نفس الخريطة والكتابة إليها في وقت واحد، فسيحدث خطأ فادح ("قراءة وكتابة متزامنة للخريطة"). في حالات التزامن، استخدم sync.Map (الدرس 16) أو أضف موتكس (الدرس 16).

📖 ملخص


📝 تمارين

  1. المسألة الأساسية (الصعوبة ⭐): استخدم map[string]int لحساب عدد مرات ظهور كل كلمة في نص ما. إذا كان المدخلات هي "the quick brown fox jumps over the lazy dog the"، فيجب أن تكون المخرجات map[the:3 quick:1 brown:1 ...].

  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): عرّف بنية Student (Name string, Scores []int) وقم بتنفيذ الطرق التالية: (1) Average() float64 لحساب متوسط الدرجات؛ (2) Grade() string لإرجاع درجة (A/B/C/D/F)؛ (3) اختبر البرنامج على 3 طلاب على الأقل.

  3. مشكلة التحدي (الصعوبة ⭐⭐⭐): قم بتنفيذ تطبيق قائمة جهات الاتصال: قم بتخزين جهات الاتصال باستخدام map[string]Contact (حيث يتضمن Contact الاسم، ورقم الهاتف، والبريد الإلكتروني، والمجموعة)؛ وقم بتنفيذ (1) الإضافة والحذف والبحث؛ (2) التصفية حسب المجموعة (العائلة/الأصدقاء/العمل)؛ (3) التصدير إلى ملف JSON (باستخدام os.WriteFile). المتطلبات: معالجة كاملة للأخطاء + علامات JSON + ما لا يقل عن 10 جهات اتصال للاختبار.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%