1.相关概念
1.开篇
最近忙着看加密,以太坊的代码。今天打算换个口味想有必要在把Go的进阶一下。毕竟最近都在用Golang 但是 Golang的底层都没有接触过。突然想起来暑假在公司上班的时候有人问我slice的一些用法:为什么作为参数的时候,直接修改会对原有的值有改变,而用了append以后就还是保留原来的样子。我的回答就是 在golang里面对array,slice,map的操作全部都是指针操作,所以第一种是直接修改了指定地址里面的数据,第二种append的话会自己产生一个新的slice所以对本来的数据没有影响。基本的测试代码大概这样的:
func main() {
a:=[]int{1,2,3,4,5,6}
test(a)
fmt.Println(a) //=>输出[10,2,3,4,5,6]
}
func test(input []int){
input[0]=10
}
func main() {
a:=[]int{1,2,3,4,5,6}
test2(a)
fmt.Println(a) => 输出[1,2,3,4,5,6]
}
func test2(input []int){
input = append(input,10)
}
一直想深追一下底层代码看下是不是跟我理解的相同,由于当时上班太忙把这件事都给忘了,今天刚好周五 又想起来了这件事所以就想深究下golang的底层操作。
2.slice的构成
slice跟array的区别就是,slice是一个可以扩容的array,有点类似java中的arraylist。挺有必要去看下slice的结构和一些方法的,重点看append。首先先找到slice的源码(找了挺久: gopath/src/runtime/slice.go)
type slice struct {
array unsafe.Pointer //=>指向一个array
len int
cap int
}
slice的结构体里面也就3个参数,array,len,cap。 array就是一个指针指向数组的一个指针。也就是说slice其实是对array指针操作的一个封装而已。
len当前slice的长度,cap当前slice的容量(cap>=len)
2.Slice
1.创建
func makeslice(et *_type, len, cap int) slice {
// NOTE: The len > maxElements check here is not strictly necessary,
// but it produces 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 issue 4085.
maxElements := maxSliceCap(et.size)
if len < 0 || uintptr(len) > maxElements {
panic(errorString("makeslice: len out of range"))
}
if cap < len || uintptr(cap) > maxElements {
panic(errorString("makeslice: cap out of range"))
}
p := mallocgc(et.size*uintptr(cap), et, true)
return slice{p, len, cap}
}
主要做一些长度和容量的判断这些到底无所谓,最后看
p := mallocgc(et.size*uintptr(cap), et, true)
return slice{p, len, cap}
也就是开辟了一块地址,p指向这个地址,最后生成了slice。 所以说slice就是对array指针操作的一层封装
这个方面的下面还有64位的操作,最后也是调用了这个方法
2.append
先解释下append的作用就是在slice的最后一个元素在添加内容。就是链表里面的加加加加加。追了好久 append最后的实现= =, 由于append 内建函数所以不提供源码,反正网上找来找去,找到了一些信息 最后追踪到:
//参数为 类型,以前的slice 和最小需要的容量
func growslice(et *_type, old slice, cap int) slice {
//raceenabled 和msanenabled 都为false 直接略过
if raceenabled {
callerpc := getcallerpc(unsafe.Pointer(&et))
racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
}
if msanenabled {
msanread(old.array, uintptr(old.len*int(et.size)))
}
if et.size == 0 {
if cap < old.cap {
panic(errorString("growslice: cap out of range"))
}
// append should not create a slice with nil pointer but non-zero len.
// We assume that append doesn't need to preserve old.array in this case.
return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
//新的容量的规则
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
//如果当前的长度小于1024 那么就直接翻倍,否则增加的量会比较小
if old.len < 1024 {
newcap = doublecap
} else {
for newcap < cap {
newcap += newcap / 4
}
}
}
//所有元素的长度,新的所有元素的长度 和容量下元素的长度
var lenmem, newlenmem, capmem uintptr
const ptrSize = unsafe.Sizeof((*byte)(nil))
switch et.size {
case 1:
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
newcap = int(capmem)
case ptrSize:
lenmem = uintptr(old.len) * ptrSize
newlenmem = uintptr(cap) * ptrSize
capmem = roundupsize(uintptr(newcap) * ptrSize)
newcap = int(capmem / ptrSize)
default:
lenmem = uintptr(old.len) * et.size. //原有的长度*元素的长度
newlenmem = uintptr(cap) * et.size //最小容量的长度
capmem = roundupsize(uintptr(newcap) * et.size)//容量下的长度
newcap = int(capmem / et.size)//新容量
}
if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) {
panic(errorString("growslice: cap out of range"))
}
var p unsafe.Pointer
if et.kind&kindNoPointers != 0 {
p = mallocgc(capmem, nil, false) //申请新的长度地址
memmove(p, old.array, lenmem)
memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
} else {
p = mallocgc(capmem, nil, false) //申请新的长度地址
p = mallocgc(capmem, et, true)
if !writeBarrier.enabled {
// 复制 n bytes from "from" to "to". 第一参数为to
memmove(p, old.array, lenmem)
} else {
for i := uintptr(0); i < lenmem; i += et.size {
//复制原来的值
typedmemmove(et, add(p, i), add(old.array, i))
}
}
}
//新的切片产生
return slice{p, old.len, newcap}
}
growslice 也就是给slice 扩容的,重要的代码都加上注释,也就是可以发现 是产生了一个新的slice 也就是新的array的指针跟新的长度和大小,那接下来就是往后面添加的东西了。讲真slice这方面有点像java的string,java的string的append 也是产生新的string之类的。
好的好的。就分析到这 biu biu biu 也好久没有写技术博客了,今天稍微写一下。有哪里说错的欢迎指出
之后还想写一些有关go高阶的东西,想想写什么比较好。。