ข้ามไปยังเนื้อหา

Maps

Map คือ hash table ที่ map key ชนิด K ไปยัง value ชนิด V สร้างด้วย make หรือ composite literal:

// make — เหมาะเมื่อเพิ่ม entry แบบทีละตัว
scores := make(map[string]int)
// composite literal — เหมาะเมื่อรู้ entry ล่วงหน้า
scores := map[string]int{
"Alice": 95,
"Bob": 87,
}

Key type ต้องเป็น comparable (รองรับ ==) type ที่ใช้เป็น key ได้คือ string, int, ตัวเลขทุกชนิด, boolean, pointer, และ struct ที่ field ทุกตัวเป็น comparable แต่ slice, map, และ function ไม่สามารถใช้เป็น key ได้

scores["Charlie"] = 72 // set
fmt.Println(scores["Alice"]) // get → 95
delete(scores, "Bob") // delete (ไม่มีผลถ้า key ไม่มีอยู่)

การอ่าน key ที่ไม่มีอยู่ ไม่ panic เลย — จะคืน zero value ของ value type:

fmt.Println(scores["Dave"]) // 0 — ไม่ใช่ panic

แม้จะสะดวก แต่ก็ซ่อนบั๊กได้: คุณไม่รู้ว่า key map ไปที่ 0 หรือไม่เคยถูก set ใช้ comma-ok idiom เพื่อแยกแยะ

การ lookup map แบบสอง result คืนค่าและ boolean ที่บอกว่า key มีอยู่หรือไม่:

v, ok := scores["Dave"]
if !ok {
fmt.Println("Dave not found")
}

การ assign map จะ copy แค่ header ไม่ใช่ข้อมูล ตัวแปรทั้งสองชี้ไปยัง hash table เดียวกัน:

a := map[string]int{"x": 1}
b := a
b["x"] = 99
fmt.Println(a["x"]) // 99 — a และ b แชร์ map เดียวกัน

Go จงใจสุ่มลำดับ map iteration ทุกครั้งที่รันโปรแกรม เพื่อป้องกันโค้ดที่พึ่งพาลำดับที่ไม่ได้นิยาม หากต้องการ print entry ของ map แบบ deterministic ให้ sort key ก่อน:

import "sort"
keys := make([]string, 0, len(scores))
for k := range scores {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%s: %d\n", k, scores[k])
}
package main
import (
"fmt"
"sort"
)
func main() {
// make a map and add entries
scores := make(map[string]int)
scores["Alice"] = 95
scores["Bob"] = 87
scores["Charlie"] = 72
// Comma-ok idiom
v, ok := scores["Bob"]
fmt.Println(v, ok) // 87 true
v2, ok2 := scores["Dave"]
fmt.Println(v2, ok2) // 0 false
// Delete an entry
delete(scores, "Charlie")
// Sort keys for deterministic output
keys := make([]string, 0, len(scores))
for k := range scores {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%s: %d\n", k, scores[k])
}
}
สิ่งที่ได้ประโยชน์ต้นทุน
built-in hash mapO(1) average lookup, ใช้งานง่ายไม่ thread-safe — ต้องใช้ mutex หรือ sync.Map
zero value for missing keyไม่ panic เมื่ออ่าน key ที่ไม่มีซ่อน bug ถ้าลืมใช้ comma-ok idiom
dynamic sizingmap ขยายอัตโนมัติmap ไม่คืน memory หลัง delete — ต้องสร้างใหม่ถ้าต้องการ shrink
randomized iterationป้องกัน code ที่พึ่งพา order โดยไม่ตั้งใจต้อง sort key ถ้าต้องการ deterministic output
  • map safe สำหรับ concurrent access — map ไม่ใช่ — goroutine หลายตัวอ่าน/เขียนพร้อมกันทำให้เกิด race condition และ crash
  • iteration order ของ map เป็น insertion order — Go map iterate แบบ random order โดยเจตนา ทุก run อาจต่างกัน
  • delete() ลด memory ทันที — Go map ไม่คืน memory ที่ใช้กับ bucket หลัง delete — สร้าง map ใหม่ถ้าต้องการ shrink
  • len(m) slow เหมือน iteratelen(map) เป็น O(1) Go track count ไว้ใน map header

💡 ตัวอย่างจากของจริง

Kubernetes ใช้ map[string]string สำหรับ pod label, annotation, และ config — label selector ก็เป็น map เช่นกัน

HTTP servers ใช้ map[string][]string สำหรับ HTTP headers (http.Header) — protect ด้วย mutex ใน concurrent handler

คุณอ่าน `scores["Dave"]` แต่ไม่เคยเพิ่ม "Dave" เข้าไป Go คืนค่าอะไร?
ค่า return ที่สองใน `v, ok := scores["Alice"]` บอกอะไร?
คุณ assign `b := a` โดยที่ a คือ `map[string]int` แล้วกำหนด `b["x"] = 99` ค่าของ `a["x"]` คืออะไร?
ทำไม Go ถึงสุ่มลำดับ map iteration?