Java 开发者初学 Go,最容易在这里“翻车”:
- array 和 slice 看起来都像 List
- slice 传参怎么会改到原数据?
- append 到底会不会影响原切片?
- 为什么 Go 还保留数组这种东西?
这篇一次把 数组 vs 切片的本质、使用方式和坑讲清楚。
一、一句话结论(先记住)
数组是值类型,切片是“指向数组的视图”。
Go 几乎只用切片,数组主要用于类型约束和底层实现。
二、数组(array):值类型,很“硬”
定义数组
var a [3]int
b := [3]int{1, 2, 3}
- 长度是 类型的一部分
- [3]int 和 [4]int 是 不同类型
func f(arr [3]int) {}
func g(arr [4]int) {}
f(b) // ok
g(b) // ❌
数组是值拷贝(重点)
a := [3]int{1, 2, 3}
b := a
b[0] = 100
fmt.Println(a) // [1 2 3]
fmt.Println(b) // [100 2 3]
完全拷贝
行为更接近 Java 的 int[] 被 Arrays.copyOf
数组的使用场景(很少)
- 固定长度(如协议、位图)
- 作为 map key
- 性能极端敏感场景
三、切片(slice):Go 的灵魂
定义切片
var s []int // nil slice
s1 := []int{1, 2, 3}
s2 := make([]int, 3) // [0 0 0]
对 Java 开发者来说:
slice ≈ List + 指针语义
slice 的内部结构(超级重大)
type slice struct {
ptr *T // 指向底层数组
len int
cap int
}
slice 不是数组,而是 描述数组的一段
四、切片是“引用语义”(高频坑)
func modify(s []int) {
s[0] = 100
}
arr := []int{1, 2, 3}
modify(arr)
fmt.Println(arr) // [100 2 3]
这和 Java List 超级像。
五、切片截取(共享底层数组)
s := []int{1, 2, 3, 4, 5}
a := s[1:3] // [2 3]
b := s[2:4] // [3 4]
a[0] = 200
fmt.Println(s) // [1 200 3 4 5]
⚠️ 多个切片可能共享同一块内存
六、append 的真相(面试高频)
情况 1:容量足够(共用数组)
s := make([]int, 0, 5)
s = append(s, 1)
不会分配新数组
情况 2:容量不够(触发扩容)
s := []int{1, 2, 3}
t := append(s, 4)
- 可能分配新数组
- t 和 s 可能不再共享内存
⚠️ 所以:
func add(s []int) {
s = append(s, 10)
}
不会影响外部 slice 长度
七、copy:显式拷贝
dst := make([]int, len(src))
copy(dst, src)
这是你“断开引用”的唯一正确方式。
八、nil slice vs 空 slice(细节)
var s1 []int // nil
s2 := []int{} // empty
|
对比 |
nil slice |
空 slice |
|
len |
0 |
0 |
|
cap |
0 |
0 |
|
== nil |
true |
false |
|
append |
ok |
ok |
序列化 / JSON 时区别很大。
九、for-range 遍历切片的坑(经典)
for _, v := range s {
v = 100
}
❌ 不会修改原切片
正确写法:
for i := range s {
s[i] = 100
}
十、Java vs Go 对照总结
|
维度 |
Java List |
Go slice |
|
是否拷贝 |
引用 |
引用 |
|
扩容 |
自动 |
自动 |
|
线程安全 |
可选 |
不安全 |
|
底层可见 |
不可见 |
可感知(len/cap) |
十一、一句话总结
数组是“数据本体”,切片是“操作视图”。
在 Go 里:能用 slice,就不要用 array。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...