findingsea's Studio.

Go slice 扩容问题

Word count: 588 / Reading time: 2 min
2018/08/17 Share

本文主要讨论 slice 扩容时对原数组的影响。

slice 是 Go 语言中的动态数组,具体的使用方法可以参考:Go by Example: Slices

slice 有两种创建方式,一种是直接创建,如:

1
s := make([]int, 4)

另一种是从数组中创建,如:

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

第一种是比较简单的场景,如果是从数组中分片创建的,那有一个场景是需要特殊注意的:扩容

还是上面这段代码,先看一下它在内存里的结构是怎么样的。

Go slice struct

slice 有三个属性字段:长度、容量和指向数组的指针。如果是直接创建的,数组则在创建时新建;如果是从数组中创建的,slice 中的数组指针就指向原数组。那么这就关系到一个问题:修改 slice 时,也会修改原数组吗?

答案好像很显然:会!因为 slice 其实没有额外分配空间,修改 slice 的元素就是修改其数组指针指向的空间嘛,就是原数组嘛。这个答案当然是对的,但是这里有特殊情况,那么就是 slice 扩容。

这里要分两种情况:

  1. slice 扩容后,还没有触及到原数组的容量,那么 slice 中的数组指针依然指向原数组。
  2. slice 扩容后,超过了原数组的容量,那么 Go 会开辟一块新的内存,把原数组拷贝进去,slice 中的数组指针指向新数组。

写代码验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
arr := [4]int{0, 2, 0, 0}
s1 := arr[:2]
s1[0] = 1
fmt.Println(arr) // [1 2 0 0]

s2 := append(s1, 3)
fmt.Println(arr) // [1 2 3 0]

s3 := append(s2, 4)
fmt.Println(arr) // [1 2 3 4]

s4 := append(s3, 5)
fmt.Println(arr) // [1 2 3 4]

s4[0] = 0
fmt.Println(arr) // [1 2 3 4]
fmt.Println(s4) // [0 2 3 4 5]

从代码和结果输出里就能很清晰地看到,在 slice 扩容没有超过原数组容量(也就是 4)时,所有对 slice 的操作,其实都是在原数组上原地修改的。一旦 slice 扩容超过了原数组的容量,那么 slice 指向的就是一个新数组了,对它的修改也就不会在原数组上生效了。

CATALOG