Golang中的切片算是在代码中使用频率最高的数据结构了,因此了解切片的底层实现可以让我们对切片的使用可以更加熟练和灵活。
go的版本:go version go1.16.8 windows/amd64
1、切片的数据结构
Golang中的切片定义在runtime包下的slice.go中,它在底层为一个结构体:
type slice struct {
array unsafe.Pointer // 指向数据缓冲区的指针
len int // 当前数据缓冲区使用的size
cap int // 当前缓冲区的容量
}
slice中包含三个字段,array是指向数据数组的指针,len是当前数组使用的大小,cap是整个数组的大小。
在下面的代码中打印slice的内存大小,可以看到slice占用的内存大小为24byte,三个字段各占8byte的内存。
package main
import (
"fmt"
"unsafe"
)
func main() {
var s []int
fmt.Println(unsafe.Sizeof(s))
var a int
fmt.Println(unsafe.Sizeof(a))
}
// 打印结果
24
8
由于slice定义在runtime包中,我们无法使用,因此可以自己定义一个Slice来将sliec强转为我们的Slice:
package main
import (
"fmt"
"unsafe"
)
type Slice struct {
array unsafe.Pointer
len int
cap int
}
func main() {
s := []int{
1, 2, 3, 4, 5}
for i, v := range s {
fmt.Printf("index:%d, value:%d\n", i, v)
}
sptr := (*Slice)(unsafe.Pointer(&s))
fmt.Println(sptr.len)
fmt.Println(sptr.cap)
arr := (*[5]int)(sptr.array)
for i := 0; i < 5; i++ {
fmt.Printf("%d ", arr[i])
}
fmt.Println()
}
// 打印结果
index:0, value:1
index:1, value:2
index:2, value:3
index:3, value:4
index:4, value:5
5
5
1 2 3 4 5
2、创建切片
makeslice函数用于创建一个切片。
func makeslice(et *_type, len, cap int) unsafe.Pointer {
// 该函数的作用是计算需要的空间大小,并检查是否溢出,
// 使用et.size * cap,mem为相乘的结果,overflow为是否溢出,bool类型
mem, overflow := math.MulUintptr(et.size, uintptr(cap))
if overflow || mem > maxAlloc || len < 0 || len > cap {
mem, overflow := math.MulUintptr(et.size, uintptr(len))
if overflow || mem > maxAlloc || len < 0 {
panicmakeslicelen()
}
panicmakeslicecap()
}
// 分配内存
// 小对象从当前P的cache中空闲数据中分配
// 大的对象 (size > 32KB) 直接从heap中分配
// runtime/malloc.go
return mallocgc(mem, et, true)
}
func makeslice64(et *_type, len64, cap64 int64) unsafe.Pointer {
len := int(len64)
if int64(len) != len64 {
panicmakeslicelen()
}
cap := int(cap64)
if int64(cap) != cap64 {
panicmakeslicecap()
}
return makeslice(et, len, cap)
}
可以看到在makeslice中做的事情就是先检查传入的参数的合法性,以及计算需要的内存是否合法,不合法就panic。最后分配内存,返回指针。但是在上面的代码中应该只是创建了底层数组的内存,slice结构体并没有创建。
2、Slice的动态增长策略
growslice函数处理append期间的数组动态增长。
// et为切片存放的数据的类型,old为旧的slice,cap为期望的容量大小
func growslice(et *_type, old slice, cap int) slice {
... // 调试相关
if cap < old.cap {
panic(errorString("growslice: cap out of range")

本文深入探讨了Go语言中切片的底层实现,包括切片的数据结构、内存占用、创建过程以及动态增长策略。在动态增长策略部分,详细分析了扩容规则的变化,指出在不同版本中增长策略的优化。同时,文章强调了切片传递时的注意事项,解释了因值传递导致的修改切片长度不一致的问题,并给出了实例演示。此外,还讨论了如何正确处理函数中切片的修改,以避免数据丢失或意外行为。
最低0.47元/天 解锁文章
874

被折叠的 条评论
为什么被折叠?



