Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > go map性能

Go Map 性能详解

作者:运维开发笔记

Go Map的性能核心在于‌底层哈希表 + 渐进式扩容‌,平均时间复杂度 O(1),但实际表现受初始化、键类型和并发方式影响很大,本文给大家介绍Go Map 性能的相关知识,感兴趣的朋友跟随小编一起看看吧

1. Map 预分配容量 —make(map[K]V, capacity)

知道 map 大致大小时,预分配容量减少扩容开销:

package main
import (
    "fmt"
    "time"
)
func main() {
    // 预分配 1000 个键值对的空间(减少扩容)
    largeMap := make(map[string]int, 1000)
    // 基准测试:插入 10000 个键值对
    fmt.Println("Benchmarking map insertion...")
    start := time.Now()
    for i := 0; i < 10000; i++ {
        largeMap[fmt.Sprintf("key%d", i)] = i * 2
    }
    insertTime := time.Since(start)
    fmt.Printf("Time to insert 10,000 items: %v\n", insertTime)
}

执行结果:

Benchmarking map insertion...
Time to insert 10,000 items: 1.7603ms

要点:

2. Map 查找性能 — O(1) vs 切片的 O(n)

map 的查找是 O(1) 常数时间,切片的线性搜索是 O(n):

package main
import (
    "fmt"
    "time"
)
func main() {
    // 创建 map 和切片,都存 10000 个元素
    largeMap := make(map[string]int, 1000)
    var slice []string
    for i := 0; i < 10000; i++ {
        largeMap[fmt.Sprintf("key%d", i)] = i * 2
        slice = append(slice, fmt.Sprintf("key%d", i))
    }
    // 基准测试:100000 次 map 查找
    fmt.Println("Benchmarking map lookup...")
    start := time.Now()
    var found int
    for i := 0; i < 100000; i++ {
        key := fmt.Sprintf("key%d", i%10000)
        if _, exists := largeMap[key]; exists {
            found++
        }
    }
    lookupTime := time.Since(start)
    fmt.Printf("Time for 100,000 lookups: %v (found %d items)\n", lookupTime, found)
    // 对比:切片线性搜索 vs map 查找
    fmt.Println("\nComparing map vs slice for membership testing...")
    target := "key5000"
    // 切片:逐个遍历查找(O(n))
    start = time.Now()
    var foundInSlice bool
    for _, item := range slice {
        if item == target {
            foundInSlice = true
            break
        }
    }
    sliceSearchTime := time.Since(start)
    // map:直接键查找(O(1))
    start = time.Now()
    _, foundInMap := largeMap[target]
    mapSearchTime := time.Since(start)
    fmt.Printf("Slice search time: %v (found: %t)\n", sliceSearchTime, foundInSlice)
    fmt.Printf("Map search time: %v (found: %t)\n", mapSearchTime, foundInMap)
    if mapSearchTime.Nanoseconds() > 0 {
        fmt.Printf("Map is %dx faster for lookup\n", sliceSearchTime.Nanoseconds()/mapSearchTime.Nanoseconds())
    }
}

执行结果:

Benchmarking map lookup...
Time for 100,000 lookups: 10.5644ms (found 100000 items)

Comparing map vs slice for membership testing...
Slice search time: 0s (found: true)
Map search time: 0s (found: true)

要点:

3. Map 删除不释放内存 — 需重建 map

删除 map 元素后,内存不会自动缩小:

package main
import "fmt"
func main() {
    memoryMap := make(map[int]string)
    for i := 0; i < 1000; i++ {
        memoryMap[i] = fmt.Sprintf("value_%d", i)
    }
    fmt.Printf("Map with 1000 items created\n")
    // 删除 90% 的元素
    for i := 0; i < 900; i++ {
        delete(memoryMap, i)
    }
    fmt.Printf("Deleted 900 items, %d items remaining\n", len(memoryMap))
    // map 仍占用 1000 个元素的内存空间!
    fmt.Println("Note: Maps don't automatically shrink memory after deletions")
    // 如果需要释放内存,必须创建新 map:
    // newMap := make(map[int]string, len(memoryMap))
    // for k, v := range memoryMap { newMap[k] = v }
    // memoryMap = newMap  // 旧 map 由 GC 回收
}

执行结果:

Map with 1000 items created
Deleted 900 items, 100 items remaining
Note: Maps don't automatically shrink memory after deletions

要点:

4. Map 遍历性能 — 大量数据遍历

遍历 map 所有键值对的性能:

package main
import (
    "fmt"
    "time"
)
func main() {
    iterMap := make(map[string]int)
    for i := 0; i < 10000; i++ {
        iterMap[fmt.Sprintf("item%d", i)] = i
    }
    start := time.Now()
    var sum int
    for _, value := range iterMap {
        sum += value
    }
    iterTime := time.Since(start)
    fmt.Printf("Time to iterate and sum 10,000 items: %v (sum: %d)\n", iterTime, sum)
}

执行结果:

Time to iterate and sum 10,000 items: 0s (sum: 49995000)

要点:

5. 键长度对性能的影响 — 短键更快

哈希计算需要遍历键的全部内容,短键哈希更快:

package main
import (
    "fmt"
    "time"
)
func main() {
    shortKeys := make(map[string]int)
    longKeys := make(map[string]int)
    // 短键:只用数字作为键 "0", "1", "2", ...
    start := time.Now()
    for i := 0; i < 1000; i++ {
        shortKeys[fmt.Sprintf("%d", i)] = i
    }
    shortKeyTime := time.Since(start)
    // 长键:用很长的字符串作为键
    start = time.Now()
    for i := 0; i < 1000; i++ {
        longKeys[fmt.Sprintf("very_long_key_name_with_lots_of_characters_%d", i)] = i
    }
    longKeyTime := time.Since(start)
    fmt.Printf("Short keys insertion time: %v\n", shortKeyTime)
    fmt.Printf("Long keys insertion time: %v\n", longKeyTime)
}

执行结果:

Short keys insertion time: 570.4μs
Long keys insertion time: 612.1μs

要点:

知识点总结

知识点关键概念
预分配容量make(map[K]V, hint) 减少扩容次数,hint 不是硬限制
查找性能map O(1) vs 切片 O(n),数据量大时差距明显
删除不释放内存delete 后内存不缩小,需重建新 map 才能释放
遍历性能O(n),与切片遍历差距不大
键长度影响短键哈希更快,int 键比 string 键更高效
选型指南查找/存在性用 map,有序/遍历用切片

到此这篇关于Go Map 性能详解的文章就介绍到这了,更多相关go map性能内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文