๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด/Go

[Go] Map ์›๋ฆฌ์™€ ํ™œ์šฉ(feat. hash)

by ์„œ์•„๋ž‘๐Ÿ˜ƒ 2025. 11. 5.

์†Œ๊ฐœ

Go์˜ map์€ ๋งค์šฐ ์ค‘์š”ํ•œ ๋‚ด์žฅ ์ž๋ฃŒ๊ตฌ์กฐ๋กœ, key-value ํ˜•ํƒœ์˜ ํ•ด์‹œ ํ…Œ์ด๋ธ”(hash table) ๊ธฐ๋ฐ˜ ์ปจํ…Œ์ด๋„ˆ์ž…๋‹ˆ๋‹ค.

์›๋ฆฌ (๋‚ด๋ถ€ ๊ตฌ์กฐ)

Go์˜ map์€ hash table์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ๊ฐœ๋…

  • Key → Hashing → Bucket → Value
  • ํ•ด์‹œ ํ•จ์ˆ˜(hash(key))๋ฅผ ํ†ตํ•ด ๋ฒ„ํ‚ท(bucket) ์ธ๋ฑ์Šค๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ , ๊ทธ ๋ฒ„ํ‚ท์— key-value ์Œ์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ํ•ด์‹œ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•˜๋ฉด ๊ฐ™์€ ๋ฒ„ํ‚ท ์•ˆ์˜ ์Šฌ๋กฏ์— ์ฒด์ด๋‹(chaining) ํ˜•ํƒœ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

๋‚ด๋ถ€ ๊ตฌ์กฐ ๊ฐœ์š”

Go ๋Ÿฐํƒ€์ž„์—์„œ map์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์กฐ๋กœ ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค (runtime/map.go ์ฐธ๊ณ ):

type hmap struct {
    count     int           // ์š”์†Œ ๊ฐœ์ˆ˜
    flags     uint8
    B         uint8         // ๋ฒ„ํ‚ท ์ˆ˜ = 2^B
    buckets   unsafe.Pointer // ์‹ค์ œ ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ
    oldbuckets unsafe.Pointer // ๋ฆฌ์‚ฌ์ด์ง• ์ค‘์ธ ๋ฒ„ํ‚ท
    ...
}
  • ๋ฒ„ํ‚ท ์ˆ˜(bucket count): 2^B
  • ๋ฆฌ์‚ฌ์ด์ง•(resize): ์š”์†Œ๊ฐ€ ๋งŽ์•„์ง€๋ฉด ์ž๋™์œผ๋กœ ๋ฒ„ํ‚ท ํฌ๊ธฐ๊ฐ€ 2๋ฐฐ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
  • Load factor (ํ‰๊ท  ๋ฒ„ํ‚ท๋‹น ์›์†Œ ์ˆ˜)๊ฐ€ ์ผ์ • ๊ธฐ์ค€ ์ด์ƒ์ด๋ฉด ์žฌ๋ฐฐ์—ด(rehashing) ์ˆ˜ํ–‰.

์‚ฌ์šฉ๋ฒ•

๊ธฐ๋ณธ ์„ ์–ธ ๋ฐ ์ดˆ๊ธฐํ™”

// ๋นˆ ๋งต ์„ ์–ธ
var m map[string]int  // nil ์ƒํƒœ, ๋ฐ”๋กœ ์‚ฌ์šฉ ๋ถˆ๊ฐ€

// make๋ฅผ ์‚ฌ์šฉํ•œ ์ดˆ๊ธฐํ™”
m = make(map[string]int)

// ๋˜๋Š” ํ•œ ์ค„ ์ดˆ๊ธฐํ™”
m := map[string]int{
    "apple":  3,
    "banana": 5,
}

์‚ฝ์ž… (Insert / Update)

m["apple"] = 10  // ์ถ”๊ฐ€ ๋˜๋Š” ์ˆ˜์ •

์กฐํšŒ (Lookup)

val := m["apple"]     // ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด 0 (int์˜ zero value)
val, ok := m["apple"] // ok == true์ด๋ฉด ์กด์žฌ

์‚ญ์ œ (Delete)

delete(m, "apple")

์ˆœํšŒ (Iteration)

for key, value := range m {
    fmt.Println(key, value)
}

Go์˜ map์€ iteration ์ˆœ์„œ๊ฐ€ ๋งค๋ฒˆ ๋žœ๋ค(randomized) ์ž…๋‹ˆ๋‹ค. ๋ณด์•ˆ์„ฑ๊ณผ ํ•ด์‹œ ๊ณต๊ฒฉ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ์„ค๊ณ„์ƒ ์ˆœ์„œ๊ฐ€ ๋ณด์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

์„ฑ๋Šฅ ๋ฐ ๋™์ž‘ ํŠน์„ฑ

์žฅ์ 

  • ํ‰๊ท  ์ ‘๊ทผ ์‹œ๊ฐ„ O(1)
  • ํ•ด์‹œ ๊ธฐ๋ฐ˜์ด๋ผ ๋น ๋ฅด๊ณ  ํšจ์œจ์ 
  • ํ‚ค ํƒ€์ž…์œผ๋กœ comparableํ•œ ํƒ€์ž… ์‚ฌ์šฉ ๊ฐ€๋Šฅ (== ๋น„๊ต ๊ฐ€๋Šฅํ•œ ํƒ€์ž…)

์ฃผ์˜์‚ฌํ•ญ

  1. Concurrent access ๊ธˆ์ง€
    • ์—ฌ๋Ÿฌ ๊ณ ๋ฃจํ‹ด์ด ๋™์‹œ์— ์ฝ๊ธฐ/์“ฐ๊ธฐ ํ•˜๋ฉด fatal error: concurrent map read and map write
    • ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•: sync.Mutex ๋˜๋Š” sync.RWMutex๋กœ ๋ณดํ˜ธํ•˜๊ฑฐ๋‚˜, sync.Map ์‚ฌ์šฉ
    var mu sync.RWMutex
    mu.Lock()
    m["x"] = 1
    mu.Unlock()
  2. Nil map ์ฃผ์˜
  3. var m map[string]int m["a"] = 1 // panic! (nil map์— ํ• ๋‹น ๋ถˆ๊ฐ€)
  4. ์ˆœ์„œ ๋ณด์žฅ X
    • ์ถœ๋ ฅ ์ˆœ์„œ๊ฐ€ ๋งค๋ฒˆ ๋‹ค๋ฆ„ → ์ˆœ์„œ๋ฅผ ์›ํ•œ๋‹ค๋ฉด slice๋กœ key๋ฅผ ์ •๋ ฌํ•ด์•ผ ํ•จ.

์˜ˆ์ œ: ๋นˆ๋„ ์นด์šดํŠธ

func main() {
    words := []string{"apple", "banana", "apple", "orange", "banana"}
    freq := make(map[string]int)

    for _, w := range words {
        freq[w]++ // ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด 0์—์„œ ์‹œ์ž‘
    }

    for k, v := range freq {
        fmt.Printf("%s: %d\\n", k, v)
    }
}

๊ด€๋ จ ๊ฐœ๋… ์ถ”์ฒœ

  • sync.Map: concurrent-safe map (lock-free, atomic ์ ‘๊ทผ ์ง€์›)
  • map[string]interface{}: JSON ํŒŒ์‹ฑ ์‹œ ์ž์ฃผ ์‚ฌ์šฉ
  • struct{}: value๋ฅผ ๋นˆ ๊ตฌ์กฐ์ฒด๋กœ ๋‘์–ด set์ฒ˜๋Ÿผ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
set := make(map[string]struct{}) 
set["A"] = struct{}{} 
if _, ok := set["A"]; ok { fmt.Println("exists") }

Go์˜ map์€ ํ•ด์‹œ ๊ธฐ๋ฐ˜์˜ ํ‚ค-๊ฐ’ ์ž๋ฃŒ๊ตฌ์กฐ๋กœ, ํ‰๊ท  ์ ‘๊ทผ ์‹œ๊ฐ„์€ O(1), ๋‚ด๋ถ€์ ์œผ๋กœ ๋ฒ„ํ‚ท ๋ฆฌ์‚ฌ์ด์ง•๊ณผ ํ•ด์‹œ ๋ถ„์‚ฐ์„ ํ†ตํ•ด ์•ˆ์ •์ ์ธ ์„ฑ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ๋™์‹œ์„ฑ ์ œ์–ด์™€ ์ˆœ์„œ ๋น„๋ณด์žฅ์€ ๋ฐ˜๋“œ์‹œ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋Œ“๊ธ€