在Go语言中,数组和切片是两种核心的数据结构。它们在实际开发中有不同的应用场景和优势。本篇文章带你详细了解。 
1. 数组(Array)
特点:
- 固定长度(长度是类型的一部分,如
[5]int
和[10]int
是不同类型) - 值类型(赋值或传参时会复制整个数组)
- 内存连续分配,访问高效
实际应用场景:
(1) 存储固定大小的数据集合
go
// 存储一周的每日温度
var weeklyTemps [7]float64
weeklyTemps = [7]float64{18.5, 20.1, 22.3, 21.7, 19.4, 17.8, 16.2}
(2) 内存敏感场景(避免堆分配)
数组在栈上分配,适合小规模固定数据:
go
// 存储RGBA像素值(固定4字节)
type Pixel [4]byte // R, G, B, A
pixel := Pixel{255, 0, 0, 255} // 红色
(3) 加密/编码等底层操作
需精确控制内存布局时:
go
// MD5哈希值存储(固定16字节)
var hash [16]byte
copy(hash[:], md5.Sum(data))
2. 切片(Slice)
特点:
- 动态长度(底层引用数组,可动态扩容)
- 引用类型(传递时仅复制切片头,不复制底层数据)
- 内置操作(
append
, 切片表达式s[i:j]
)
实际应用场景:
(1) 动态数据集合(最常见用法)
go
// 读取文件内容(长度未知)
data, _ := os.ReadFile("file.txt") // data 是 []byte 切片
lines := strings.Split(string(data), "\n")
// 动态添加用户
users := []string{"Alice", "Bob"}
users = append(users, "Charlie") // 自动扩容
(2) 复用内存(避免复制)
go
// 截取子集(共享底层数组)
log := []byte("2023-10-01 ERROR: Disk full")
errorMsg := log[11:] // "ERROR: Disk full"(无数据复制)
(3) 实现栈/队列
go
// 栈操作
stack := []int{}
stack = append(stack, 1) // 压栈
top := stack[len(stack)-1] // 取栈顶
stack = stack[:len(stack)-1] // 弹栈
// 队列(需谨慎,出队可能导致底层数组移位)
queue := []int{1,2,3}
queue = queue[1:] // 出队(2,3)
(4) 高性能批量处理
go
// 分批处理任务(复用内存)
batchSize := 100
data := make([]int, 0, 10000) // 预分配容量
for i := 0; i < 10000; i++ {
data = append(data, i)
if len(data) == batchSize {
processBatch(data) // 处理批次
data = data[:0] // 重置切片(底层数组保留)
}
}
(5) 避免内存泄漏
go
func ReadBigFile() []byte {
data := make([]byte, 0, 10*1024*1024) // 预分配10MB
// ...填充数据...
return data[:len(data)] // 返回实际大小的切片
}
特性 | 数组 (Array) | 切片 (Slice) |
---|---|---|
长度 | 固定(编译时确定) | 动态(运行时可扩展) |
内存分配 | 值类型(复制整个数据) | 引用类型(复制切片头) |
传递开销 | 大(适合小数组) | 小(适合大型数据) |
适用场景 | 固定大小、内存敏感、栈分配 | 动态数据、共享内存、高频操作 |
3. 最佳实践
-
优先用切片:95%的场景使用切片(动态性更灵活)。
-
预分配容量 :
go// 已知最大规模时预分配 s := make([]int, 0, 1000) // 长度0,容量1000
-
避免意外修改 :
go// 返回切片的拷贝(防止外部修改底层数组) func SafeReturn() []int { data := []int{1, 2, 3} return append([]int{}, data...) // 复制数据 }
-
数组转切片 :
goarr := [3]int{1,2,3} slice := arr[:] // 转为切片(共享内存)
4. 性能陷阱
应用时不够谨慎可能会带来一些麻烦:
-
切片扩容 :
append
可能触发复制(容量不足时)。 -
大数组传参:值复制代价高,应改用切片或指针。
-
内存泄漏 :大切片的小子切片会阻止整个底层数组被GC回收:
gobigData := make([]byte, 100*1024*1024) // 100MB smallPart := bigData[:10] // 保留整个100MB内存! // 解决方案:复制需要的数据 smallPart := make([]byte, 10) copy(smallPart, bigData)
以上就是二者对比。
总结
- 数组:固定大小数据、栈分配敏感、精确内存控制(如加密/硬件交互)。
- 切片:动态数据集合、高效内存复用、数据分块处理(90%+场景)。