Στη Go, το map είναι ένας ενσωματωμένος τύπος δεδομένων που αναπαριστά πίνακα κατακερματισμού.
Σε άλλες γλώσσες προγραμματισμού, μπορεί να ξέρεις το map ως dict ή συσχετιστικό πίνακα ή αποθήκη κλειδιών/τιμών.
Αν δεν είσαι εξοικειωμένος με αυτή την έννοια, μπορείς να φανταστείς το map σαν ένα λεξικό, όπου κάθε λέξη είναι το key και ο ορισμός είναι το element του map.
Πριν ξεκινήσουμε, θα ήθελα να σε παραπέμψω στις προδιαγραφές της Go για το
mapκαι στο blog της Go για τοmap, για να εμβαθύνεις περισσότερο σταmap.
Συντακτικά, το map μοιάζει κάπως έτσι:
map[KeyType]ElementType
Το KeyType πρέπει να είναι οποιουδήποτε συγκρίσιμου τύπου, ενώ το ElementType μπορεί να είναι οποιοσδήποτε έγκυρος τύπος στη Go, που σημαίνει ότι μπορείς να αποθηκεύσεις τα πάντα, από μια πρωτογενή μεταβλητή μέχρι ένα slice.
Είναι σημαντικό να θυμάσαι ότι το map στη Go δεν έχει σειρά. Αν προσπαθήσεις να διατρέξεις ένα map με έναν βρόχο και να εκτυπώσεις τα στοιχεία, μπορεί να εκπλαγείς βλέποντάς τα να εκτυπώνονται με τυχαία σειρά (δοκίμασέ το αν θέλεις).
Είναι επίσης σημαντικό να ξέρεις ότι κάθε κλειδί είναι μοναδικό, που σημαίνει ότι αν ορίσεις το ίδιο κλειδί δύο φορές, θα αντικαταστήσεις την τιμή του αντίστοιχου κλειδιού.
Το map είναι τύπος αναφοράς, που σημαίνει ότι αν το περάσεις κάπου, η Go δεν θα αντιγράψει ολόκληρο το map.
Αντ' αυτού, η Go θα αντιγράψει τον δείκτη του map, κάτι που κάνει το πέρασμα ενός map σε μια συνάρτηση ή μεταβλητή φθηνό.
Η τιμή ενός μη αρχικοποιημένου map είναι nil.
Μπορείς να ορίσεις ένα map ως εξής (αυτό το λέμε επίσης nil map):
var foo map[string]int
Για να αρχικοποιήσεις ένα map, μπορείς να κάνεις:
// With map literal
foo := map[string]int{}
ή
// or with make function
foo := make(map[string]int)
Ένα nil map είναι διαφορετικό από ένα αρχικοποιημένο map, η εγγραφή σε ένα nil map θα προκαλέσει σφάλμα χρόνου εκτέλεσης
panic: assignment to entry in nil mapΓι' αυτό είναι σημαντικό να αρχικοποιείς ένα map πριν το χρησιμοποιήσεις
Ακολουθούν μερικές πράξεις που μπορείς να κάνεις με ένα map
// Add a value in a map with the `=` operator:
foo["bar"] = 42
// Here we update the element of `bar`
foo["bar"] = 73
// To retrieve a map value, you can use
baz := foo["bar"]
// To delete an item from a map, you can use
delete(foo, "bar")
Αν προσπαθήσεις να ανακτήσεις την τιμή για ένα κλειδί που δεν υπάρχει στο map, θα σου επιστραφεί η μηδενική τιμή του τύπου τιμών.
Αυτό μπορεί να σε μπερδέψει, ειδικά αν η προεπιλεγμένη τιμή του ElementType (για παράδειγμα, 0 για έναν int) είναι μια έγκυρη τιμή.
Για να ελέγξεις αν ένα κλειδί υπάρχει στο map σου, μπορείς να χρησιμοποιήσεις
value, exists := foo["baz"]
// If the key "baz" does not exist,
// value: 0; exists: false
Όπως είδαμε προηγουμένως, το να εντοπίσεις αν ένα map είναι αρχικοποιημένο ή όχι είναι εύκολο, μπορούμε απλώς να το συγκρίνουμε με το nil. Τι γίνεται όμως με άλλα map; Λοιπόν, εφόσον το map δεν είναι συγκρίσιμο, δεν μπορούμε να χρησιμοποιήσουμε τον τελεστή ==, αλλά το πακέτο reflect έχει κάτι να μας σώσει, που λέγεται DeepEqual, και μπορούμε να το χρησιμοποιήσουμε έτσι
import "reflect"
// ....
equal := reflect.DeepEqual(map[string]int{}, map[string]int{})
fmt.Println(equal)
// Output: true
Μα περίμενε, αν το map δεν είναι συγκρίσιμο, γιατί μπορούμε να το συγκρίνουμε με το nil; Λοιπόν, οι προδιαγραφές έχουν κάνει μια εξαίρεση γι' αυτό, δες τις προδιαγραφές για τους συγκρίσιμους τύπους
Το τελευταίο: αν προσπαθήσεις να γράψεις σε ένα map από πολλαπλά goroutines, αυτό θα ενεργοποιήσει τον race detector, δες τα Go Docs και το The Go Blog.
Εναλλακτικά, μπορείς να χρησιμοποιήσεις το sync.Map ή το atomic ή το mutex για να παρακάμψεις αυτό το ζήτημα.