Go 数组与切片详解(面向 Java 开发者)

内容分享1小时前发布
0 0 0

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。

© 版权声明

相关文章

暂无评论

none
暂无评论...