最近排查了一个问题,最后发现是由于对golang的切片实现不熟悉导致踩了坑。
切片的底层结构由数组指针,切片容量cap,切片当前长度len组成。
一、切片的三种初始化方式的区别
- 字面值
会根据传入的字面值初始化底层数组并创建切片
例子
arr := []int{1, 2, 3}
- 下标初始化
会创建一个指向原数组的切片结构体,不会拷贝原数组或者原切片中的数据,所以修改新切片的数据也会修改原切片。
例子
arr := [3]int{1, 2, 3}
slice := arr[0:1]
- make关键字初始化
会根据传入的len和cap创建底层数组并创建切片
arr := make([]int, 10)
二、切片复制
- 使用下标,从旧切片生成新切片
例子
arr := []int{1, 2, 3, 4}
arr2 := arr[0:2] // 1, 2
此时,两个切片共用底层数组结构,对arr2中的值进行修改会造成对arr的修改。事实上,只要是对底层公共数组的修改,都会作用到切片上。
func TestSlice(t *testing.T) {
arr1 := []int{1, 2, 3, 4}
arr2 := arr1[:2]
fmt.Println(arr1) //[1 2 3 4]
fmt.Println(arr2) //[1 2]
arr2[0] = 5
fmt.Println(arr1) //[5 2 3 4]
fmt.Println(arr2) //[5 2]
arr2 = append(arr2, 6)
fmt.Println(arr1) //[5 2 6 4]
fmt.Println(arr2) //[5 2 6]
}
插入数据时,如果底层数组容量足够则会修改原来的数组;否则会引起扩容,从而新建一个底层数组。
容量充足
func TestSlice(t *testing.T) {
arr1 := make([]int, 3, 4) //len 3 cap 4
arr1[0] = 1
arr1[1] = 2
arr1[2] = 3
arr2 := arr1[2:] //len 1 cap 2
fmt.Println(arr1) //[1 2 3]
fmt.Println(arr2) //[3]
arr2 = append(arr2, 6) //arr2容量足够,仍然修改原来的数组
fmt.Println(arr1) //[1 2 3]
fmt.Println(arr2) //[3 6]
arr1 = append(arr1, 7)
fmt.Println(arr1) //[1 2 3 7]
fmt.Println(arr2) //[3 7]
}
容量不足
func TestSlice(t *testing.T) {
arr1 := []int{1, 2, 3, 4} //len 4 cap 4
arr2 := arr1[2:] //len 2 cap 2
fmt.Println(arr1) //[1 2 3 4]
fmt.Println(arr2) //[3 4]
arr2 = append(arr2, 6) //arr2 扩容生成新数组,底层不再共用
arr2[0] = 5
fmt.Println(arr1) //[1 2 3 4]
fmt.Println(arr2) //[5 4 6]
}
总结,使用下标复制分片的方式会使用共同的底层数组,如果需要对分片进行修改,有可能对原始数据造成修改。
- copy拷贝
指定原切片和目标切片的大小,copy会将内存中的数据进行复制,属于深度拷贝。
func TestSlice(t *testing.T) {
arr1 := []int{1, 2, 3, 4}
arr2 := []int{5, 6}
fmt.Println(arr1) //[1 2 3 4]
fmt.Println(arr2) //[5 6]
copy(arr2, arr1)
fmt.Println(arr1) //[1 2 3 4]
fmt.Println(arr2) //[1 2]
arr2[0] = 5
fmt.Println(arr1) //[1 2 3 4]
fmt.Println(arr2) //[5 2]
}
参考资料
https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-array-and-slice/#32-%E5%88%87%E7%89%87