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) // true2.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)
}
}总结对比
| 特性 | Array | Slice | Map |
|---|---|---|---|
| 类型 | 值类型 | 引用类型 | 引用类型 |
| 大小 | 固定 | 动态增长 | 动态增长 |
| 底层 | 连续内存 | 数组引用 | 哈希表 |
| 并发 | 安全 | 不安全 | 不安全 |
| 零值 | [0,0,...] | nil | nil |
最佳实践:
- 使用
make预分配 slice 容量,避免频繁扩容 - 并发读写 map 用
sync.RWMutex或sync.Map - 切片截取后注意底层数组共享问题
- 大结构体传指针,小结构体传值
作者:PFinal南丞 | 更新时间:2026-04-21

