golang源码分析--slice

Peren ·
更新时间:2024-11-14
· 812 次阅读

切片基础概念:

切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。(注意:切片传递的是指针的拷贝值,所以可以在函数里面修改指针指向的值,对外有影响)

切片的自动增长是通过append()函数来实现的

切片的底层内存也是在连续块中分配的,所以切片还能获得索引,迭代以及为垃圾回收优化的好处。

源码分析: 结构体定义 type slice struct { array unsafe.Pointer len int cap int }

slice的结构体包含三个参数,指针,长度,容量
所以传递一个切片切片需要24字节的内存:指针字段需要8字节,长度和容量字段分别需要8字节。

切片作为函数传递需要注意的地方:

1.切片作为函数的参数在函数内改变值
切片作为参数可以节省空间,但是需要注意的是切片传递过去的值为指针,所以在函数中改变切片指向的值,在函数外也会有影响,这是由切片结构体内存储的指针特性决定的

func changevalue(arr []int){ arr[0] = 2 fmt.Println(&arr[0]) } func main() { arr:=make([]int,0,2) arr = append(arr,1) fmt.Println(arr) changevalue(arr) fmt.Println(arr) fmt.Println(&arr[0]) }

输出

[1] 0xc0000601b0 [2] 0xc0000601b0

2.切片在函数中的append操作
append未在源码内找到实现方式,但是append的作用如下:
append函数将元素追加到slice的结尾,若切片还有生效的容量,则值直接追加到连续内存的后面。
若追加的值得数目大于切片的原容量,则申请一个新的底层数组,append返回更新的slice。
要注意,切片作为参数在函数中传递本质是是值传递,特殊点是它的值中有一个指针
不改变值得方式,append追加不改变函数外面的值,PS:哪怕追加至更改内存

func appendarrNoChange(arr []int){ arr = append(arr,2) fmt.Println(&arr[0]) } func main() { arr:=make([]int,0,2) arr = append(arr,1) fmt.Println(arr) appendarrNoChange(arr) fmt.Println(arr) fmt.Println(&arr[0]) } [1] 0xc0000601b0 [1] 0xc0000601b0 func appendarrNoChange(arr []int){ arr = append(arr,2) arr = append(arr,3) fmt.Println(&arr[0]) } func main() { arr:=make([]int,0,2) arr = append(arr,1) fmt.Println(arr) appendarrNoChange(arr) fmt.Println(arr) fmt.Println(&arr[0]) } [1] 0xc00005e2a0 [1] 0xc0000601b0

若需要append在函数生效:

func appendarrChange(arr []int)[]int{ arr = append(arr,2) fmt.Println(&arr[0]) arr = append(arr,3) fmt.Println(&arr[0]) return arr } func main() { arr:=make([]int,0,2) arr = append(arr,1) fmt.Println(arr) arr = appendarrChange(arr) fmt.Println(arr) fmt.Println(&arr[0]) }

可以用append()完毕后获取到的新切片替代旧切片在函数外赋值

[1] 0xc00000a210 0xc00000e500 [1 2 3] 0xc00000e500 切片的初始化: func makeslice(et *_type, len, cap int) unsafe.Pointer { //返回类型占的字节数目*容量值=要开辟的内存字节数 mem, overflow := math.MulUintptr(et.size, uintptr(cap)) //比对乘法是否溢出,要申请的内存是否少于能提供的最大内存 if overflow || mem > maxAlloc || len cap { // NOTE: Produce a 'len out of range' error instead of a // 'cap out of range' error when someone does make([]T, bignumber). // 'cap out of range' is true too, but since the cap is only being // supplied implicitly, saying len is clearer. // See golang.org/issue/4085. mem, overflow := math.MulUintptr(et.size, uintptr(len)) if overflow || mem > maxAlloc || len < 0 { panicmakeslicelen() } panicmakeslicecap() } //申请内存 return mallocgc(mem, et, true) }

关于malloc分配的策略(因为本篇以slice为主,关于内存分配的部分放到后面详解,在此大致介绍):
若对象很小(<32kb),则从per-P缓存的空闲列表中分配空间(为单个goroutine分配的线程空间,不存在并发,所以使用的时候不用加锁,小对象分配在这上面运行效率会很高)
若对象大于32kb,则直接从堆中获取内存。

append增加内存的规律:

func growslice(et *_type, old slice, cap int) slice { if raceenabled { callerpc := getcallerpc() racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice)) } if msanenabled { msanread(old.array, uintptr(old.len*int(et.size))) } //禁止缩小容量 if cap doublecap { newcap = cap } else { //若旧的长度小于1024,则乘以两倍,否则增加1.25倍 if old.len < 1024 { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { newcap += newcap / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap maxAlloc newcap = int(capmem) case et.size == sys.PtrSize: lenmem = uintptr(old.len) * sys.PtrSize newlenmem = uintptr(cap) * sys.PtrSize capmem = roundupsize(uintptr(newcap) * sys.PtrSize) overflow = uintptr(newcap) > maxAlloc/sys.PtrSize newcap = int(capmem / sys.PtrSize) case isPowerOfTwo(et.size): var shift uintptr if sys.PtrSize == 8 { // Mask shift for better code generation. shift = uintptr(sys.Ctz64(uint64(et.size))) & 63 } else { shift = uintptr(sys.Ctz32(uint32(et.size))) & 31 } lenmem = uintptr(old.len) << shift newlenmem = uintptr(cap) << shift capmem = roundupsize(uintptr(newcap) < (maxAlloc >> shift) newcap = int(capmem >> shift) default: lenmem = uintptr(old.len) * et.size newlenmem = uintptr(cap) * et.size capmem, overflow = math.MulUintptr(et.size, uintptr(newcap)) capmem = roundupsize(capmem) newcap = int(capmem / et.size) } // The check of overflow in addition to capmem > maxAlloc is needed // to prevent an overflow which can be used to trigger a segfault // on 32bit architectures with this example program: // // type T [1< maxAlloc { panic(errorString("growslice: cap out of range")) } var p unsafe.Pointer if et.kind&kindNoPointers != 0 { p = mallocgc(capmem, nil, false) // The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length). // Only clear the part that will not be overwritten. memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem) } else { // Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory. p = mallocgc(capmem, et, true) if writeBarrier.enabled { // Only shade the pointers in old.array since we know the destination slice p // only contains nil pointers because it has been cleared during alloc. bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem) } } memmove(p, old.array, lenmem) return slice{p, old.len, newcap} }

函数append会智能地处理底层数组的容量增长。在切片的容量小于1024个元素时,总是会成倍地增加容量,一旦元素个数超过1000,容量的增长因子会设为1.25,每次增长25%,实验如下。

func main() { arr:=make([]int,0,2) s:=cap(arr) for i:=0;is{ s = t fmt.Println(t) } } } 内容: 4 8 16 32 64 128 256 512 1024 1280 1696 2304
作者:hello_bravo_



slice golang

需要 登录 后方可回复, 如果你还没有账号请 注册新账号