本文将介绍 slice 的底层原理,在此基础上,我们能更好地了解深拷贝和浅拷贝具体做了什么。根据示例解释各种特殊情况下两种拷贝方式的影响。
基础知识
在分析拷贝结果之前,我们需要了解以下概念:
- slice 实际上是一个结构体,它保存了[^internal]:
- 底层 array 片段在内存中的地址
- 底层 array 的长度(capacity)
- slice 自身的长度(length)
- 内存地址
- 修改 slice 中的元素不会影响 slice 指向的底层 array 的地址
- 如果 slice 追加元素没有超过底层 array 的长度(capacity),那么不会影响 slice 指向的底层 array 的地址,只是修改了对应地址的元素,并修改了 slice 的 length 属性。
- 如果 slice 追加元素超过了 capacity,那么 Golang 会给 slice 重新分配一块更大的内存空间,将原内存空间中的数据复制到新空间中,并追加新元素。这时 slice 指向了新的地址。
浅拷贝
浅拷贝复制的是 slice 对象,也就是上面提到的三个字段。复制的 slice 和源 slice 的指向的地址空间、length、capacity 都相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package main
import ( "fmt" "reflect" "unsafe" )
func main() { slice1 := []int{1, 2, 3, 4, 5} slice1 = slice1[:4] slice2 := slice1 fmt.Println(slice1) fmt.Println(slice2) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice1))) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice2)))
slice1[1] = 100 fmt.Println(slice1) fmt.Println(slice2) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice1))) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice2)))
slice1 = append(slice1, 200) fmt.Println(slice1) fmt.Println(slice2) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice1))) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice2)))
slice2 = append(slice2, 400) fmt.Println(slice1) fmt.Println(slice2) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice1))) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice2)))
slice1 = append(slice1, 300) fmt.Println(slice1) fmt.Println(slice2) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice1))) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice2))) }
|
现在我们根据示例代码来分析两个 slices 指向同一个内存地址意味着什么:
如果修改两个 slices 都包含的元素,会相互影响。
slice1
后面追加元素,没有导致重新分配地址,两个 slices 还指向同一个底层 array。但是只有 slice1
的 length 发生了改变,所以两个 slices 的值不相同。
slice2
后面追加元素,会修改 index 4 位置的元素,所以也同时修改了 slice1
中对应位置的元素。
slice1
后追加元素超过了底层 array 的大小(capacity),所以触发重新分配地址,两个 slices 指向不同地址,不再相互影响。
深拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package main
import ( "fmt" "reflect" "unsafe" )
func main() { slice1 := []int{1, 2, 3, 4, 5, 6} slice1 = slice1[:5] slice2 := make([]int, 5, 5) slice3 := make([]int, 3, 4) slice4 := make([]int, 6, 6) copy(slice2, slice1) copy(slice3, slice1) copy(slice4, slice1) fmt.Println(slice1) fmt.Println(slice2) fmt.Println(slice3) fmt.Println(slice4) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice1))) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice2))) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice3))) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice4)))
slice1[1] = 100 fmt.Println(slice1) fmt.Println(slice2) fmt.Println(slice3) fmt.Println(slice4) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice1))) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice2))) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice3))) fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&slice4))) }
|
深拷贝是使用 copy
函数将源 slice 包含的元素拷贝到目标 slice 中。
从示例可以看到,只属于底层 array 的元素不会被拷贝。同时 copy
只会影响目标 slice 中索引小于源 slice length 的元素,其他元素将保持不变。
[^internal]: 参考文章 Go Slices: usage and internals - go.dev。