【Golang】Slice底层原理
Slice的底层原理
传递类型
Golang的Slice在函数参数传递的时候是值传递,但效果又呈现出了引用的效果(不完全是引用传递)
现象
当在函数中修改元素值的时,原来的Slice中的值同样被修改了,展示出了【引用传递】的效果。
func checkSlice(course []string) {
course[0] = "java"
}
func main() {
course := []string{"go", "grpc", "orm"}
checkSlice(course)
fmt.Println(course)
}
//输出:[java grpc orm]
但在函数中添加元素时,原来的Slice并没有被添加该值,展示出了【值传递】的效果。
func checkSlice(course []string) {
course = append(course, "java")
}
func main() {
course := []string{"go", "grpc", "orm"}
checkSlice(course)
fmt.Println(course)
}
//输出:[go grpc orm]
分析原理
slice的结构体
type slice struct {
array unsafe.Pointer // 用来存储实际数据的数组指针,指向一块连续的内存
len int // 切片中元素的数量
cap int // array数组的长度(容量)
}
当定义一个[]string{”go”,”grpc”,”orm”}
时,会先生成一个结构体,array
指向内存空间的第一个值,len
长度为3,cap
容量为3。
当
mySlice
作为参数进行函数调用时,会将mySlice
对象复制一份,也就是所谓的【值传递】。 因此在函数中对切片做值的修改,原来的切片值也会随之改变。
但是在函数中添加数据时,原切片的容量不够,需要重新开辟一块内存空间,将旧值复制进去,当容量小于512时,扩容的容量是原来的2倍(大于512时,翻1.5倍)。其扩容机制类似于java中的ArrayList(1.5倍)。 因此在函数中做添加操作时,会开辟新的内存,原切片指向的地址无法指向新的内存空间。
其他细节(过程分析)
知道了原因那么该怎么解决呢? 考虑到在创建切片的时候,cap容量已经被固定下来了,那么是否可以在创建切片的时候指定cap容量来实现函数中添加元素的效果?
func checkSlice(course []string) {
course = append(course, "java")
}
func main() {
course := make([]string, 3, 6)
course[0] = "go"
course[1] = "grpc"
course[2] = "orm"
checkSlice(course)
fmt.Println(course)
}
// 输出:["go","grpc","orm"]
可以看到输出并没有变化,新的元素也并没有被加进来。 按理来说这时添加数据的时候不应该重新开辟内存,因为容量是足够的。 于是我们可以在函数中将第一个元素的值进行修改并输出。
func checkSlice(course *[]string) {
course = append(course, "java")
course[0] = "gogogo"
}
// 输出:["gogogo","grpc","orm"]
可以看到第一个元素确实被修改了,说明并没有开辟新的内存,但”java“并没有被添加进来。 其实只要我们再分析的细致一点就可以发现:在函数传递时,拷贝的是slice对象,它会将slice中array
,len
,cap
的值一起拷贝然后生成新的slice对象。
func checkSlice(course []string) {
course = append(course, "java")
course[0] = "gogogo"
println("func-slice len:",len(course))
}
func main() {
//course := []string{"go", "grpc", "orm"}
course := make([]string, 3, 6)
course[0] = "go"
course[1] = "grpc"
course[2] = "orm"
checkSlice(course)
fmt.Println("main-slice len:",len(course))
fmt.Println(course)
}
/*
输出:
func-slice len: 4
main-slice len: 3
[gogogo grpc orm]
*/
通过在两个函数中输出slice的长度可以看出:在cap
容量充足的情况下添加元素,调用函数中的slice对象的len+1
,但原来的slice并没有+1。这就是问题所在,其实在内存中已经添加了”java“,只不过在main
函数中输出的时候并没有访问到该元素,因为此时main
中slice的长度依旧是3
解决方案
一个解决方案是函数调用的时候传递slice的地址。但总的来说还是不建议在其他函数中对原slice进行添加数据操作。