Golang 切片的底层源码原理是什么,怎么理解
导读:这篇文章主要给大家介绍“Golang 切片的底层源码原理是什么,怎么理解”的相关知识,下文通过实际案例向大家展示操作过程,内容简单清晰,易于学习,有这方面学习需要的朋友可以参考,希望这篇“Golang 切片的底层源码原理是什么,怎么理解”文...
这篇文章主要给大家介绍“Golang 切片的底层源码原理是什么,怎么理解”的相关知识,下文通过实际案例向大家展示操作过程,内容简单清晰,易于学习,有这方面学习需要的朋友可以参考,希望这篇“Golang 切片的底层源码原理是什么,怎么理解”文章能对大家有所帮助。
数组
说切片前先说下数组。数组的两个特性
- 一段连续内存地址,每个元素都是连续的
- 元素的类型相同,并且元素个数固定
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)底层源码的详细内容,更多请关注php中文网其它相关文章!
到此这篇关于“Golang 切片的底层源码原理是什么,怎么理解”的文章就介绍到这了,感谢各位的阅读,更多相关Golang 切片的底层源码原理是什么,怎么理解内容,欢迎关注网络资讯频道,小编将为大家输出更多高质量的实用文章!
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: Golang 切片的底层源码原理是什么,怎么理解
本文地址: https://pptw.com/jishu/654622.html
