Announcement

👇Official Account👇

图片

Welcome to join the group & private message

Article first/tail QR code

Skip to content

Go 数组、切片与 Map 深度解析

切片和 Map 是 Go 最常用的数据结构。理解它们的底层实现,能帮你写出更安全、高效的代码。

一、数组

数组在 Go 中是值类型,长度固定,是切片的底层基础。

go
// 声明与初始化
var arr1 [5]int                          // 零值初始化 [0 0 0 0 0]
arr2 := [3]string{"Go", "PHP", "Python"}
arr3 := [...]int{1, 2, 3, 4, 5}         // 自动推断长度

// 访问与修改
fmt.Println(arr2[0])  // Go
arr2[0] = "Golang"

// 遍历
for i, v := range arr3 {
    fmt.Printf("arr3[%d] = %d\n", i, v)
}

// 数组是值类型:赋值会复制
a := [3]int{1, 2, 3}
b := a          // 完整复制
b[0] = 100
fmt.Println(a)  // [1 2 3]  a 不受影响
fmt.Println(b)  // [100 2 3]

多维数组

go
// 2x3 矩阵
matrix := [2][3]int{
    {1, 2, 3},
    {4, 5, 6},
}

for _, row := range matrix {
    for _, v := range row {
        fmt.Printf("%d ", v)
    }
    fmt.Println()
}

二、切片(Slice)

切片是 Go 中最常用的序列类型,本质上是对底层数组的引用

2.1 切片的底层结构

Slice Header(24 字节,64位系统):
┌──────────────────┬───────┬──────┐
│  Pointer (8B)    │ Len   │ Cap  │
│  底层数组指针     │ 长度  │ 容量 │
└──────────────────┴───────┴──────┘
go
// 创建切片的多种方式
s1 := []int{1, 2, 3}              // 字面量
s2 := make([]int, 5)              // 长度=5, 容量=5
s3 := make([]int, 3, 10)          // 长度=3, 容量=10
var s4 []int                      // nil 切片,长度容量都为 0

fmt.Println(len(s3), cap(s3))     // 3 10
fmt.Println(s4 == nil)            // true

2.2 切片操作

go
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

// 切片表达式 [low:high:max]
a := s[2:5]         // [2 3 4],len=3, cap=8
b := s[2:5:7]       // [2 3 4],len=3, cap=5(cap限制为7-2=5)

// append 追加元素
s = append(s, 10, 11, 12)

// 合并两个切片
a := []int{1, 2, 3}
b := []int{4, 5, 6}
c := append(a, b...)  // [1 2 3 4 5 6]

// copy 复制
dst := make([]int, len(a))
n := copy(dst, a)     // 返回复制的元素数量

2.3 切片扩容机制

go
s := make([]int, 0)
for i := 0; i < 10; i++ {
    s = append(s, i)
    fmt.Printf("len=%d cap=%d\n", len(s), cap(s))
}
// 输出(Go 1.18+ 的新扩容策略):
// len=1 cap=1
// len=2 cap=2
// len=3 cap=4
// len=4 cap=4
// len=5 cap=8
// ...

扩容规则(Go 1.18 起):

  • 若需要容量 > 当前容量的 2 倍,直接使用所需容量
  • 若原容量 < 256,新容量翻倍
  • 若原容量 >= 256,每次增长约 25%(逐步接近 1.25 倍)

2.4 切片陷阱:共享底层数组

go
original := []int{1, 2, 3, 4, 5}
slice := original[1:3]   // [2, 3]

// 修改 slice 会影响 original!
slice[0] = 100
fmt.Println(original)    // [1 100 3 4 5]

// 安全做法:使用 copy 创建独立副本
safeCopy := make([]int, len(original[1:3]))
copy(safeCopy, original[1:3])
safeCopy[0] = 999
fmt.Println(original)    // [1 100 3 4 5] 不受影响

2.5 切片删除元素

go
s := []int{1, 2, 3, 4, 5}

// 删除索引 i 的元素(保持顺序)
i := 2
s = append(s[:i], s[i+1:]...)
fmt.Println(s) // [1 2 4 5]

// 删除索引 i 的元素(不保持顺序,性能更好)
s[i] = s[len(s)-1]
s = s[:len(s)-1]

2.6 实用切片技巧

go
// 切片去重
func unique(s []int) []int {
    seen := make(map[int]struct{})
    result := s[:0]  // 复用底层数组
    for _, v := range s {
        if _, ok := seen[v]; !ok {
            seen[v] = struct{}{}
            result = append(result, v)
        }
    }
    return result
}

// 反转切片
func reverse(s []int) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}

// 过滤切片
func filter(s []int, f func(int) bool) []int {
    result := s[:0]
    for _, v := range s {
        if f(v) {
            result = append(result, v)
        }
    }
    return result
}

三、映射(Map)

3.1 创建与使用

go
// 创建 map
m1 := map[string]int{
    "apple":  5,
    "banana": 3,
    "cherry": 8,
}

m2 := make(map[string]int)        // 空 map
m2["key"] = 100

var m3 map[string]int             // nil map,不能直接写入!

// 读取(带存在性检查)
v, ok := m1["apple"]
if ok {
    fmt.Println("apple:", v)      // apple: 5
}

// 不存在的 key 返回零值
fmt.Println(m1["orange"])         // 0(int 的零值)

// 删除
delete(m1, "banana")

// 遍历(顺序不确定!)
for k, v := range m1 {
    fmt.Printf("%s: %d\n", k, v)
}

// 获取 map 长度
fmt.Println(len(m1))

3.2 Map 的底层原理

Go 的 map 使用哈希表实现:

  • 由多个 bucket(桶)组成,每个桶存 8 个键值对
  • 负载因子超过 6.5 时触发扩容
  • 哈希冲突使用链地址法解决
Map 内存布局:
┌─────────────────────────────┐
│  hmap 结构体                 │
│  count: 元素数量             │
│  buckets: 桶数组指针          │
│  oldbuckets: 旧桶(扩容时用)│
└─────────────────────────────┘


┌──────────────┬──────────────┐
│   bucket[0]  │   bucket[1]  │ ...
│  8 个 kv 对  │  8 个 kv 对  │
└──────────────┴──────────────┘

3.3 并发安全:sync.Map

原生 map 不是并发安全的

go
// ❌ 危险:并发读写会 panic
m := make(map[int]int)
go func() { m[1] = 1 }()
go func() { _ = m[1] }()

// ✅ 方案1:sync.RWMutex 保护
type SafeMap struct {
    mu sync.RWMutex
    m  map[string]int
}

func (s *SafeMap) Set(k string, v int) {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.m[k] = v
}

func (s *SafeMap) Get(k string) (int, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    v, ok := s.m[k]
    return v, ok
}

// ✅ 方案2:sync.Map(读多写少场景)
var sm sync.Map

sm.Store("key", "value")

v, ok := sm.Load("key")
if ok {
    fmt.Println(v)  // value
}

sm.Delete("key")

sm.Range(func(k, v any) bool {
    fmt.Printf("%v: %v\n", k, v)
    return true  // 返回 false 停止遍历
})

3.4 Map 常见用法

go
// 嵌套 map
config := map[string]map[string]string{
    "database": {
        "host": "localhost",
        "port": "5432",
    },
    "cache": {
        "host": "localhost",
        "port": "6379",
    },
}

// 用 map 模拟 Set
type Set map[string]struct{}

func NewSet(items ...string) Set {
    s := make(Set)
    for _, item := range items {
        s[item] = struct{}{}
    }
    return s
}

func (s Set) Contains(item string) bool {
    _, ok := s[item]
    return ok
}

// 用 map 统计词频
func wordFrequency(text string) map[string]int {
    freq := make(map[string]int)
    for _, word := range strings.Fields(text) {
        freq[strings.ToLower(word)]++
    }
    return freq
}

// 按 value 排序 map
func sortMapByValue(m map[string]int) []string {
    type kv struct {
        Key   string
        Value int
    }
    var pairs []kv
    for k, v := range m {
        pairs = append(pairs, kv{k, v})
    }
    sort.Slice(pairs, func(i, j int) bool {
        return pairs[i].Value > pairs[j].Value
    })
    var keys []string
    for _, p := range pairs {
        keys = append(keys, p.Key)
    }
    return keys
}

四、结构体(Struct)

go
// 定义结构体
type User struct {
    ID       int
    Name     string
    Email    string
    Age      int
    IsActive bool
    CreatedAt time.Time
}

// 初始化
u1 := User{
    ID:    1,
    Name:  "张三",
    Email: "zhangsan@example.com",
    Age:   25,
}

// 指针接收者(推荐用于大型结构体)
func (u *User) String() string {
    return fmt.Sprintf("User{ID: %d, Name: %s}", u.ID, u.Name)
}

// 值接收者
func (u User) IsAdult() bool {
    return u.Age >= 18
}

// 构造函数(Go 惯例)
func NewUser(id int, name, email string) *User {
    return &User{
        ID:        id,
        Name:      name,
        Email:     email,
        IsActive:  true,
        CreatedAt: time.Now(),
    }
}

结构体嵌入(组合代替继承)

go
type Animal struct {
    Name string
}

func (a Animal) Speak() string {
    return a.Name + " 发出声音"
}

// Dog 嵌入 Animal
type Dog struct {
    Animal               // 嵌入
    Breed string
}

func (d Dog) Speak() string {
    return d.Name + " 汪汪叫"  // 重写
}

d := Dog{
    Animal: Animal{Name: "旺财"},
    Breed:  "柴犬",
}
fmt.Println(d.Name)   // 旺财(提升字段)
fmt.Println(d.Speak()) // 旺财 汪汪叫

结构体标签(Tag)

go
type Product struct {
    ID    int     `json:"id" db:"id"`
    Name  string  `json:"name" db:"name" validate:"required,min=1,max=100"`
    Price float64 `json:"price,omitempty" db:"price"`
}

// JSON 序列化
p := Product{ID: 1, Name: "iPhone", Price: 999.99}
data, _ := json.Marshal(p)
fmt.Println(string(data))
// {"id":1,"name":"iPhone","price":999.99}

// 反序列化
var p2 Product
json.Unmarshal(data, &p2)

五、接口(Interface)

go
// 定义接口
type Animal interface {
    Sound() string
    Name() string
}

// 实现接口(隐式,无需 implements 关键字)
type Dog struct{ name string }
func (d Dog) Sound() string { return "汪汪" }
func (d Dog) Name() string  { return d.name }

type Cat struct{ name string }
func (c Cat) Sound() string { return "喵喵" }
func (c Cat) Name() string  { return c.name }

// 多态使用
func makeSound(a Animal) {
    fmt.Printf("%s 说:%s\n", a.Name(), a.Sound())
}

makeSound(Dog{name: "旺财"})  // 旺财 说:汪汪
makeSound(Cat{name: "咪咪"})  // 咪咪 说:喵喵

// 空接口(any)
var anything any = 42
anything = "hello"
anything = []int{1, 2, 3}

// 类型断言
if s, ok := anything.(string); ok {
    fmt.Println("字符串:", s)
}

// 类型 switch
func describe(i any) {
    switch v := i.(type) {
    case int:
        fmt.Printf("整数: %d\n", v)
    case string:
        fmt.Printf("字符串: %s\n", v)
    case []int:
        fmt.Printf("整数切片, 长度: %d\n", len(v))
    default:
        fmt.Printf("未知类型: %T\n", v)
    }
}

总结对比

特性ArraySliceMap
类型值类型引用类型引用类型
大小固定动态增长动态增长
底层连续内存数组引用哈希表
并发安全不安全不安全
零值[0,0,...]nilnil

最佳实践:

  • 使用 make 预分配 slice 容量,避免频繁扩容
  • 并发读写 map 用 sync.RWMutexsync.Map
  • 切片截取后注意底层数组共享问题
  • 大结构体传指针,小结构体传值

作者:PFinal南丞 | 更新时间:2026-04-21

上次更新于: