本文是小编一年前发表在公司公众号上的一篇文章,最近没时间写文章,暂时拿来充数吧。
前言
很多人对Go语言的array和slice傻傻分不清楚,今天我们就从底层出发,来聊聊它俩到底有什么区别。
数组
几乎所有计算机语言,数组的实现都是相似的:一段连续的内存,Go语言也一样,Go语言的数组底层实现就是一段连续的内存空间。每个元素有唯一一个索引(或者叫下标)来访问。如下图所示,下图是[5]int{1:10, 2:20}数组的内部实现逻辑图:
由于内存连续,CPU很容易计算索引(即数组的下标),可以快速迭代数组里的所有元素。
Go语言的数组不同于 C语言 或者其他语言的数组,C语言的数组变量是指向数组第一个元素的指针;而Go语言的数组是一个值,Go语言中的数组是值类型,一个数组变量就表示着整个数组,意味着Go语言的数组在传递的时候,传递的是原数组的拷贝。你可以理解为Go语言的数组是一种有序的 struct
slice
切片是一个很小的对象,是对数组进行了抽象,并提供相关的操作方法。切片有三个属性字段:长度、容量和指向数组的指针。
上图中,ptr指的是指向array的pointer,len是指切片的长度, cap指的是切片的容量。现在,我想你对数组和切片有了一个本质的认识。
3.1 切片有多种声明方式,每种初始化方式对应的逻辑图是怎样的呢?
1)对于s := make([]byte, 5)和s := []byte{…}的方式
2)对于s = s[2:4]的方式
3)对于 nil 的切片即var s []byte对应的逻辑图
在此有一个说明:nil切片和空切片是不太一样的,空切片即s := make([]byte, 0)或者s := []byte{}出来的切片
空切片的逻辑图为:
空切片指针不为nil,而nil切片指针为nil。但是,不管是空切片还是nil切片,对其调用内置函数append()、len和cap的效果都是一样的,感受不到任何区别。
扩容
slice这种数据结构便于使用和管理数据集合,可以理解为是一种“动态数组”,slice也是围绕动态数组的概念来构建的。既然是动态数组,那么slice是如何扩容的呢?
请记住以下两条规则:
1)如果切片的容量小于1024个元素,那么扩容的时候slice的cap就翻番,乘以2;一旦元素个数超过1024个元素,增长因子就变成1.25,即每次增加原来容量的四分之一。
2)如果扩容之后,还没有触及原数组的容量,那么,切片中的指针指向的位置,就还是原数组,如果扩容之后,超过了原数组的容量,那么,Go就会开辟一块新的内存,把原来的值拷贝过来,这种情况丝毫不会影响到原数组。
知道了一下规则,请看下面程序,试问输出结果:
1import ( 2 "fmt" 3) 4func main(){ 5 array := [4]int{10, 20, 30, 40} 6 slice := array[0:2] 7 newSlice := append(append(append(slice, 50), 100), 150) 8 newSlice[1] += 1 9 fmt.Println(slice) 10}
输出:
1[10 20]
答对了吗?
参考文献
《Go in action》
更多精彩内容,请关注我的微信公众号 互联网技术窝