数据切片的设计和注意事项
- 切片和数组的区别:
- 数组是需要指定个数的,而切片则不需要。数组赋值也可是使用如下方式,忽略元素个数,使用“...”代替
- slice和array的关系十分密切,通过两者的合理构建,既能实现动态灵活的线性结构,也能提供访问元素的高效性能。当然,这种结构也不是完美无暇,共用底层数组,在部分修改操作的时候,可能带来副作用,同时如果一个很大的数组,那怕只有一个元素被切片应用,那么剩下的数组都不会被垃圾回收,这往往也会带来额外的问题。
- 使用make创建slice,此时golang会生成一个匿名的数组。
- append操作超过了原始切片的容量,将会有一个新建底层数组的过程,那么此时再修改函数返回切片,应该不会再影响原始切片。
- nil 是 interface、function、pointer、map、slice 和 channel 类型变量的默认初始值。
- 直接使用 slice:即使函数内部得到的是 slice 的值拷贝,但依旧会更新 slice 的原始数据(底层 array),会修改 slice 的底层 array,从而修改 slice
- 看起来 Go 支持多维的 array 和 slice,可以创建数组的数组、切片的切片,但其实并不是。对依赖动态计算多维数组值的应用来说,就性能和复杂度而言,用 Go 实现的效果并不理想。可以使用原始的一维数组、“独立“ 的切片、“共享底层数组”的切片来创建动态的多维数组。
- 使用原始的一维数组:要做好索引检查、溢出检测、以及当数组满时再添加值时要重新做内存分配。
- 使用“独立”的切片分两步:
- 创建外部 slice
- 对每个内部 slice 进行内存分配,注意内部的 slice 相互独立,使得任一内部 slice 增缩都不会影响到其他的 slice
- 使用“共享底层数组”的切片
- 创建一个存放原始数据的容器 slice
- 创建其他的 slicefx
- 切割原始 slice 来初始化其他的 slice
- string 类型的值是只读的二进制 byte slice,如果真要修改字符串中的字符,将 string 转为 []byte 修改后,再转为 string 即可
- 因为一个 UTF8 编码的字符可能会占多个字节,比如汉字就需要 3~4 个字节来存储,此时更新其中的一个字节是错误的。
- 更新字串的正确姿势:将 string 转为 rune slice(此时 1 个 rune 可能占多个 byte),直接更新 rune 中的字符
- 从 slice 中重新切出新 slice 时,新 slice 会引用原 slice 的底层数组。如果跳了这个坑,程序可能会分配大量的临时 slice 来指向原底层数组的部分数据,将导致难以预料的内存使用。可以通过拷贝临时 slice 的数据,而不是重新切片来解决(特别是对于返回slice的函数,在函数里面的新slice不要引用越来的slice,而是新建一个slice的拷贝,返回这个拷贝)
测试代码
package main
import (
"bytes"
"fmt"
"reflect"
)
func main() {
makeSlice()
apendSlice1()
apendSlice2()
fmt.Println("\n当往 newSlice 中新增元素的时候,由于其容量不够,newSlice 会拥有一个全新的底层数组,其容量是原来的两倍(Go 会自动完成这个操作,一旦元素个数超过 1000,增长因子会设为 1.25)")
apendSlice3()
apendSlice4()
rangeSlice()
fmt.Println("在使用 range 遍历 slice 的时候,range 会创建每个元素的副本,每次迭代的变量的地址是相同的,说明迭代过程复用了这个变量,也是一种防止内存浪费的做法。")
equalSlice()
}
//makeSlice 数据切片的初始化
func makeSlice() {
//声明数据切片
var slice1 []int //可以直接使用,直接使用append方法
//初始化数据切片,指定数据切片的长度
slice2 := make([]int, 10)
//初始化数据切片,指定数据切片的长度和切片的最大容量
slice3 := make([]int, 10, 100)
//声明数据切片并赋值数据切片,此时数据切片的长度和最大容量相等
slice4 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Printf("slice1 = %v\n len(slice1) == %d,\t cap(slice1) == %d\n\n", slice1, len(slice1), cap(slice1))
fmt.Printf("slice2 = %v\n len(slice2) == %d,\t cap(slice2) == %d\n\n", slice2, len(slice2), cap(slice2))
fmt.Printf("slice3 = %v\n len(slice3) == %d,\t cap(slice3) == %d\n\n", slice3, len(slice3), cap(slice3))
fmt.Printf("slice4 = %v\n len(slice4) == %d,\t cap(slice4) == %d\n\n", slice4, len(slice4), cap(slice4))
}
//apendSlice1 数据切片的追加
func apendSlice1() {
oldSlice := make([]int, 10, 100)
fmt.Printf("oldSlice = %v\n len(oldSlice) == %d,\t cap(oldSlice) == %d\n\n", oldSlice, len(oldSlice), cap(oldSlice))
n, m := 1, 99 //0 <= n <= m < cap(oldSlice)
newSlice := oldSlice[n:m] //[n:m)
fmt.Printf("newSlice = %v\n len(newSlice) == %d,\t cap(newSlice) == %d\n\n", newSlice, len(newSlice), cap(newSlice))
fmt.Printf("len(newSlice) == m-n == %d, cap(newSlice) == cap(oldSlice)-n == %d\n", len(newSlice), cap(newSlice))
}
//apendSlice2 数据切片的追加
func apendSlice2() {
//数据切片是一个引用,但是容量扩充之后引用就会发生变化
fmt.Println("\n\n数据切片的复制\n")
parent_arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Printf("parent_arr = %v\n", parent_arr)
child1_slice := parent_arr[0:10]
fmt.Printf("child1_slice = %v len(child1_slice) == %d,\t cap(child1_slice) == %d\n\n", child1_slice, len(child1_slice), cap(child1_slice))
child2_slice := child1_slice[3:5]
fmt.Printf("child2_slice = %v len(child2_slice) == %d,\t cap(child2_slice) == %d\n", child2_slice, len(child2_slice), cap(child2_slice))
child2_slice = append(child2_slice, 100)
fmt.Printf("child2_slice = %v len(child2_slice) == %d,\t cap(child2_slice) == %d\n", child2_slice, len(child2_slice), cap(child2_slice))
fmt.Printf("child1_slice = %v len(child1_slice) == %d,\t cap(child1_slice) == %d\n\n", child1_slice, len(child1_slice), cap(child1_slice))
child3_slice := parent_arr[3:5]
fmt.Printf("child3_slice = %v len(child3_slice) == %d,\t cap(child3_slice) == %d\n", child3_slice, len(child3_slice), cap(child3_slice))
child3_slice = append(child3_slice, 1000)
fmt.Printf("child3_slice = %v len(child3_slice) == %d,\t cap(child3_slice) == %d\n", child3_slice, len(child3_slice), cap(child3_slice))
fmt.Printf("child1_slice = %v len(child1_slice) == %d,\t cap(child1_slice) == %d\n", child1_slice, len(child1_slice), cap(child1_slice))
fmt.Printf("child2_slice = %v len(child2_slice) == %d,\t cap(child2_slice) == %d\n", child2_slice, len(child2_slice), cap(child2_slice))
fmt.Println(&child3_slice[1], &child2_slice[1], "\n")
child4_slice := parent_arr[3:5:5]
fmt.Printf("child4_slice = %v len(child4_slice) == %d,\t cap(child4_slice) == %d\n", child4_slice, len(child4_slice), cap(child4_slice))
child4_slice = append(child4_slice, 10000)
fmt.Printf("child4_slice = %v len(child4_slice) == %d,\t cap(child4_slice) == %d\n", child4_slice, len(child4_slice), cap(child4_slice))
fmt.Printf("child1_slice = %v len(child1_slice) == %d,\t cap(child1_slice) == %d\n", child1_slice, len(child1_slice), cap(child1_slice))
fmt.Printf("child2_slice = %v len(child2_slice) == %d,\t cap(child2_slice) == %d\n", child2_slice, len(child2_slice), cap(child2_slice))
fmt.Printf("child3_slice = %v len(child3_slice) == %d,\t cap(child3_slice) == %d\n\n", child3_slice, len(child3_slice), cap(child3_slice))
child5_slice := parent_arr[3:5:6]
fmt.Printf("child5_slice = %v len(child5_slice) == %d,\t cap(child5_slice) == %d\n", child5_slice, len(child5_slice), cap(child5_slice))
child5_slice = append(child5_slice, 100000)
fmt.Printf("child5_slice = %v len(child5_slice) == %d,\t cap(child5_slice) == %d\n", child5_slice, len(child5_slice), cap(child5_slice))
fmt.Printf("child1_slice = %v len(child1_slice) == %d,\t cap(child1_slice) == %d\n", child1_slice, len(child1_slice), cap(child1_slice))
fmt.Printf("child2_slice = %v len(child2_slice) == %d,\t cap(child2_slice) == %d\n", child2_slice, len(child2_slice), cap(child2_slice))
fmt.Printf("child3_slice = %v len(child3_slice) == %d,\t cap(child3_slice) == %d\n", child3_slice, len(child3_slice), cap(child3_slice))
fmt.Printf("child4_slice = %v len(child4_slice) == %d,\t cap(child4_slice) == %d\n\n", child4_slice, len(child4_slice), cap(child4_slice))
child5_slice = append(child5_slice, 100000)
fmt.Printf("child5_slice = %v len(child5_slice) == %d,\t cap(child5_slice) == %d\n", child5_slice, len(child5_slice), cap(child5_slice))
fmt.Printf("child1_slice = %v len(child1_slice) == %d,\t cap(child1_slice) == %d\n", child1_slice, len(child1_slice), cap(child1_slice))
fmt.Printf("child2_slice = %v len(child2_slice) == %d,\t cap(child2_slice) == %d\n", child2_slice, len(child2_slice), cap(child2_slice))
fmt.Printf("child3_slice = %v len(child3_slice) == %d,\t cap(child3_slice) == %d\n", child3_slice, len(child3_slice), cap(child3_slice))
fmt.Printf("child4_slice = %v len(child4_slice) == %d,\t cap(child4_slice) == %d\n", child4_slice, len(child4_slice), cap(child4_slice))
}
//apendSlice3 数据切片的追加
func apendSlice3() {
//数据切片声明的时候,直接可以通过append添加元素
//验证数据切片的容量是按照倍数增长的
var slice []int
i := 0
for i < 10 {
fmt.Printf("slice = %v\n len(slice) == %d,\t cap(slice) == %d\n\n", slice, len(slice), cap(slice))
slice = append(slice, i)
i++
}
fmt.Printf("slice = %v\n len(slice) == %d,\t cap(slice) == %d\n\n", slice, len(slice), cap(slice))
//直接使用 slice:即使函数内部得到的是 slice 的值拷贝,但依旧会更新 slice 的原始数据(底层 array)
//会修改 slice 的底层 array,从而修改 slice
func(arr []int) {
arr[0] = 7
fmt.Println(slice)
}(slice)
fmt.Println(slice)
}
//apendSlice4 数据切片的追加
func apendSlice4() {
slice := [][]int{{10}, {100, 200}}
fmt.Printf("slice = %v\n len(slice) == %d,\t cap(slice) == %d\n\n", slice, len(slice), cap(slice))
fmt.Printf("slice[0] = %v\n len(slice[0]) == %d,\t cap(slice[0]) == %d\n\n", slice[0], len(slice[0]), cap(slice[0]))
fmt.Printf("slice[1] = %v\n len(slice[1]) == %d,\t cap(slice[1]) == %d\n\n", slice[1], len(slice[1]), cap(slice[1]))
slice[0] = append(slice[0], 20)
fmt.Printf("slice = %v\n len(slice) == %d,\t cap(slice) == %d\n\n", slice, len(slice), cap(slice))
fmt.Printf("slice[0] = %v\n len(slice[0]) == %d,\t cap(slice[0]) == %d\n\n", slice[0], len(slice[0]), cap(slice[0]))
fmt.Printf("slice[1] = %v\n len(slice[1]) == %d,\t cap(slice[1]) == %d\n\n", slice[1], len(slice[1]), cap(slice[1]))
}
//rangeSlice 数据切片的遍历
func rangeSlice() {
slice := []int{10, 20, 30, 40,} //这个","不是必须的,但是如果换行之后这个","就是必须的
// 迭代每个元素,并显示值和地址,这个值是原来元素值的一份拷贝,修改这个值并不会改变原来元素的值
for index, value := range slice {
fmt.Printf("Value: %d Value-Addr: %X ElemAddr: %X\n", value, &value, &slice[index])
value *=10
}
fmt.Println(slice)
//需要使用数据切片的引用才能实现对源数据的修改
for index, value := range slice {
fmt.Printf("Value: %d Value-Addr: %X ElemAddr: %X\n", value, &value, &slice[index])
slice[index] *=10
}
fmt.Println(slice)
}
//equalSlice 数据切片的比较
func equalSlice(){
var slice1 []byte
slice2 := []byte{}
fmt.Println("slice1 == slice1: ", bytes.Equal(slice1, slice2))
fmt.Println("slice1 == slice2: ", reflect.DeepEqual(slice1, slice2))
}
测试代码结果
slice1 = []
len(slice1) == 0, cap(slice1) == 0
slice2 = [0 0 0 0 0 0 0 0 0 0]
len(slice2) == 10, cap(slice2) == 10
slice3 = [0 0 0 0 0 0 0 0 0 0]
len(slice3) == 10, cap(slice3) == 100
slice4 = [1 2 3 4 5 6 7 8 9 10]
len(slice4) == 10, cap(slice4) == 10
oldSlice = [0 0 0 0 0 0 0 0 0 0]
len(oldSlice) == 10, cap(oldSlice) == 100
newSlice = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
len(newSlice) == 98, cap(newSlice) == 99
len(newSlice) == m-n == 98, cap(newSlice) == cap(oldSlice)-n == 99
数据切片的复制
parent_arr = [1 2 3 4 5 6 7 8 9 10]
child1_slice = [1 2 3 4 5 6 7 8 9 10] len(child1_slice) == 10, cap(child1_slice) == 10
child2_slice = [4 5] len(child2_slice) == 2, cap(child2_slice) == 7
child2_slice = [4 5 100] len(child2_slice) == 3, cap(child2_slice) == 7
child1_slice = [1 2 3 4 5 100 7 8 9 10] len(child1_slice) == 10, cap(child1_slice) == 10
child3_slice = [4 5] len(child3_slice) == 2, cap(child3_slice) == 7
child3_slice = [4 5 1000] len(child3_slice) == 3, cap(child3_slice) == 7
child1_slice = [1 2 3 4 5 1000 7 8 9 10] len(child1_slice) == 10, cap(child1_slice) == 10
child2_slice = [4 5 1000] len(child2_slice) == 3, cap(child2_slice) == 7
0xc0000880c0 0xc0000880c0
child4_slice = [4 5] len(child4_slice) == 2, cap(child4_slice) == 2
child4_slice = [4 5 10000] len(child4_slice) == 3, cap(child4_slice) == 4
child1_slice = [1 2 3 4 5 1000 7 8 9 10] len(child1_slice) == 10, cap(child1_slice) == 10
child2_slice = [4 5 1000] len(child2_slice) == 3, cap(child2_slice) == 7
child3_slice = [4 5 1000] len(child3_slice) == 3, cap(child3_slice) == 7
child5_slice = [4 5] len(child5_slice) == 2, cap(child5_slice) == 3
child5_slice = [4 5 100000] len(child5_slice) == 3, cap(child5_slice) == 3
child1_slice = [1 2 3 4 5 100000 7 8 9 10] len(child1_slice) == 10, cap(child1_slice) == 10
child2_slice = [4 5 100000] len(child2_slice) == 3, cap(child2_slice) == 7
child3_slice = [4 5 100000] len(child3_slice) == 3, cap(child3_slice) == 7
child4_slice = [4 5 10000] len(child4_slice) == 3, cap(child4_slice) == 4
child5_slice = [4 5 100000 100000] len(child5_slice) == 4, cap(child5_slice) == 6
child1_slice = [1 2 3 4 5 100000 7 8 9 10] len(child1_slice) == 10, cap(child1_slice) == 10
child2_slice = [4 5 100000] len(child2_slice) == 3, cap(child2_slice) == 7
child3_slice = [4 5 100000] len(child3_slice) == 3, cap(child3_slice) == 7
child4_slice = [4 5 10000] len(child4_slice) == 3, cap(child4_slice) == 4
当往 newSlice 中新增元素的时候,由于其容量不够,newSlice 会拥有一个全新的底层数组,其容量是原来的两倍(Go 会自动完成这个操作,一旦元素个数超过 1000,增长因子会设为 1.25)
slice = []
len(slice) == 0, cap(slice) == 0
slice = [0]
len(slice) == 1, cap(slice) == 1
slice = [0 1]
len(slice) == 2, cap(slice) == 2
slice = [0 1 2]
len(slice) == 3, cap(slice) == 4
slice = [0 1 2 3]
len(slice) == 4, cap(slice) == 4
slice = [0 1 2 3 4]
len(slice) == 5, cap(slice) == 8
slice = [0 1 2 3 4 5]
len(slice) == 6, cap(slice) == 8
slice = [0 1 2 3 4 5 6]
len(slice) == 7, cap(slice) == 8
slice = [0 1 2 3 4 5 6 7]
len(slice) == 8, cap(slice) == 8
slice = [0 1 2 3 4 5 6 7 8]
len(slice) == 9, cap(slice) == 16
slice = [0 1 2 3 4 5 6 7 8 9]
len(slice) == 10, cap(slice) == 16
[7 1 2 3 4 5 6 7 8 9]
[7 1 2 3 4 5 6 7 8 9]
slice = [[10] [100 200]]
len(slice) == 2, cap(slice) == 2
slice[0] = [10]
len(slice[0]) == 1, cap(slice[0]) == 1
slice[1] = [100 200]
len(slice[1]) == 2, cap(slice[1]) == 2
slice = [[10 20] [100 200]]
len(slice) == 2, cap(slice) == 2
slice[0] = [10 20]
len(slice[0]) == 2, cap(slice[0]) == 2
slice[1] = [100 200]
len(slice[1]) == 2, cap(slice[1]) == 2
Value: 10 Value-Addr: C000074DC8 ElemAddr: C000090060
Value: 20 Value-Addr: C000074DC8 ElemAddr: C000090068
Value: 30 Value-Addr: C000074DC8 ElemAddr: C000090070
Value: 40 Value-Addr: C000074DC8 ElemAddr: C000090078
[10 20 30 40]
Value: 10 Value-Addr: C000074E10 ElemAddr: C000090060
Value: 20 Value-Addr: C000074E10 ElemAddr: C000090068
Value: 30 Value-Addr: C000074E10 ElemAddr: C000090070
Value: 40 Value-Addr: C000074E10 ElemAddr: C000090078
[100 200 300 400]
在使用 range 遍历 slice 的时候,range 会创建每个元素的副本,每次迭代的变量的地址是相同的,说明迭代过程复用了这个变量,也是一种防止内存浪费的做法。
slice1 == slice1: true
slice1 == slice2: false