这篇文章将为大家详细讲解有关Golang切片Slice底层源码简介,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
数组
说切片前先说下数组。数组的两个特性
一段连续内存地址,每个元素都是连续的
元素的类型相同,并且元素个数固定
Go 数组是值类型,赋值和函数传参操作都会复制整个数组数据。
arr := [2]int{1,2}arr2 := arr fmt.Printf("%p %p",&arr ,&arr2)//切片slice1 := []int{1,2}slice2 := slice1 fmt.Printf("%p %p",slice1 ,slice2)
切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型.切片是一个长度可变的数组。
Slice 的数据结构定义如下:
runtime/slice.go#L13
type slice struct { array unsafe.Pointer len int cap int}
array 就是底层数组的地址
len 切片的长度
cap 切片的容量
src/runtime/slice.go#L83
func makeslice(et *_type, len, cap int) unsafe.Pointer { mem, overflow := math.MulUintptr(et.size, uintptr(cap)) .... return mallocgc(mem, et, true)}
基本逻辑就是根据容量申请一块内存。
扩容是当切片的长度大于容量的时候,底层数组已经装不下时
func growslice(et *_type, old slice, cap int) slice { ... // 如果新要扩容的容量比原来的容量还要小,直接报panic if cap < old.cap { panic(errorString("growslice: cap out of range")) } // 如果当前切片的大小为0,还调用了扩容方法,那么就新生成一个新的容量的切片返回 // []struct{} if et.size == 0 { return slice{unsafe.Pointer(&zerobase), old.len, cap} } newcap := old.cap doublecap := newcap + newcap //要扩容的容量大于2 *oldcap 新切片容量 = 该容量 if cap > doublecap { newcap = cap } else { // 旧容量 小于1024,新容量= 旧容量 * 2 也就是扩容1倍 if old.cap < 1024 { newcap = doublecap } else { // 扩容容量 = 旧容量 +旧容量*1/4 for 0 < newcap && newcap < cap { newcap += newcap / 4 } //溢出之后 新容量=要扩容的容量 if newcap <= 0 { newcap = cap } } } var overflow bool // 计算新的切片的容量,长度。 var lenmem, newlenmem, capmem uintptr .... var p unsafe.Pointer if et.ptrdata == 0 { p = mallocgc(capmem, nil, false) memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem) } else { p = mallocgc(capmem, et, true) if lenmem > 0 && writeBarrier.enabled { bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata) } } //移动到p memmove(p, old.array, lenmem) //返回slice结构,让slice.array指向p return slice{p, old.len, newcap}}
新申请容量cap,如果大于2倍旧容量(oldcap),要扩容的容量(newcap)=新申请容量cap
如果旧容量(oldcap)< 1024, 要扩容的容量(newcap)在旧容量(oldcap)基础上扩容1倍,否则则扩容 1/4
如果数值溢出,要扩容的容量 = 新申请的容量
arr := make([]int,1024) arr = append(arr,1) fmt.Println(len(arr),cap(arr))// 1025,1280 arr1 := make([]int,10) arr1 = append(arr1,1) fmt.Println(len(arr1),cap(arr1))//11 20
注意事项: 切片共享底层数组,所以在切片赋值的时候,修改切片会导致底层数组改变,而产生BUG
arr := []int{1,2,3,4} arr1 := arr[:2] //[1,2] arr1 = append(arr1,5) fmt.Println(arr[3]) //5 修改了底层数组 //例子2 arr3 := []int{1,2,3,4} arr4 := arr3[2:] arr4 = append(arr4,10)//扩容 不会影响arr3 fmt.Println(arr3)
src/runtime/slice.go#L247
//toPtr 目标地址 toLen目标长度 // width 元素大小 func slicecopy(toPtr unsafe.Pointer, toLen int, fromPtr unsafe.Pointer, fromLen int, width uintptr) int { //判断长度 if fromLen == 0 || toLen == 0 { return 0 } n := fromLen if toLen < n { n = toLen } //切片大小等于0 if width == 0 { return n } size := uintptr(n) * width //特殊处理 如果只有一个元素并且大小是1byte,那么指针直接转换即可 if size == 1 { *(*byte)(toPtr) = *(*byte)(fromPtr) } else { //从 fm.array 地址开始,拷贝到 to.array 地址之后 memmove(toPtr, fromPtr, size) } return n }
关于“Golang切片Slice底层源码简介”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。