Golang疑难杂症-数组切片(Slices 数组与切片的关系是怎样的?)
先创建一个数组,并获取数组的指针地址与数组元素内对应的指针地址
array := [5]int{1, 2, 3, 4, 5} fmt.Println(array, "array 地址为: ", unsafe.Pointer(&array), "字节数是: ", unsafe.Sizeof(array)) for i := 0; i < len(array); i++ { fmt.Printf("%d %p \n", array[i], &array[i]) }
打印结果:
我们可以看到通过unsafe.Pointer(&array)获取到的是数组第一个元素对应的指针,并且指针地址是连续的。
[1 2 3 4 5] array 地址为: 0xc00000a390 字节数是: 40 1 0xc00000a390 2 0xc00000a398 3 0xc00000a3a0 4 0xc00000a3a8 5 0xc00000a3b0
基于array数组新建切片,新建片切有多种方式,可自行了解
//切片容量计算方式为:原始数组总长度-新建切片时的startIndex=切片的容量
sliceArray := array[:] fmt.Println(sliceArray, "sliceArray 切片的长度是: ", len(sliceArray), "容量是: ", cap(sliceArray), "字节数是: ", unsafe.Sizeof(sliceArray))
// 打印结果: [1 2 3 4 5] sliceArray 切片的长度是: 5 容量是: 5 字节数是: 24
sliceArray := array[1:3]
fmt.Println(sliceArray, "sliceArray 切片的长度是: ", len(sliceArray), "容量是: ", cap(sliceArray), "字节数是: ", unsafe.Sizeof(sliceArray))
// 打印结果: [2 3] sliceArray 切片的长度是: 2 容量是: 4 字节数是: 24
按照正常理解,我们基于原数组array新增的切片数据是[1 2 3 4 5],那么它的字节数应该是 5*8=40才对(64位系统int类型占8位)。
其实是当我们创建切片时,golang会帮我们包一层SliceHeader结构: 结构体中 Data、Len、Cap 3*8=24所以当我们获取切片的字节是一直是24字节
type SliceHeader struct { Data uintptr //切片数据地址,对应的是原数组第一个元素的地址 Len int //切片的长度 Cap int //切片的容量 }
知道了SliceHeader我们换一种写法,获取切片数据的字节数:
sliceArray := array[1:3] sh := (*reflect.SliceHeader)(unsafe.Pointer(&sliceArray)) sliceData := *(*[4]int)(unsafe.Pointer(sh.Data)) fmt.Println(sliceData, "sliceData uintptr: ", sh.Data, " Len: ", sh.Len, " Cap: ", sh.Cap, "Data Byte", unsafe.Sizeof(sliceData)) //输出结果 [2 3 4 5] sliceData uintptr: 824635089048 Len: 2 Cap: 4 Data Byte 32
经过分析得知:切片实际占用的内存大小跟容量有关系。容量是多少就占用多少内存,我们平常取切片值时,只不过是通过长度取部分的数据。如代码中sliceArray的长度为2,但实际的占用字节是32字节
接下来再看看当切片长度小于容量时,对切片进行新增元素操作会发生什么
array := [5]int{1, 2, 3, 4, 5} sliceArray := array[1:3] sliceArray = append(sliceArray, 6) fmt.Println(array) // 打印结果: [1 2 3 6 5]
测试得知:容量大于长度时,对切片的新增,其实就是对原始数组的修改。
接下来再看看当切片长度等于容量时,对切片进行新增元素操作会发生什么
array := [5]int{1, 2, 3, 4, 5} sliceArray := array[:] sliceArray = append(sliceArray, 6) fmt.Println(array) // 打印结果: [1 2 3 4 5] sh := (*reflect.SliceHeader)(unsafe.Pointer(&sliceArray)) sliceData := *(*[6]int)(unsafe.Pointer(sh.Data)) fmt.Println(sliceData, "sliceData uintptr: ", sh.Data, " Len: ", sh.Len, " Cap: ", sh.Cap, "Data Byte", unsafe.Sizeof(sliceData)) //输出结果 [1 2 3 4 5 6] sliceData uintptr: 824633770624 Len: 6 Cap: 10 Data Byte 48
测试得知:当切片容量扩容时,会重新开辟一块内存给扩容后的切片,所作的操作都是基于新的内存地址进行操作。