前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >彻底搞懂golang中的数组和切片slice

彻底搞懂golang中的数组和切片slice

原创
作者头像
J_J
发布2018-10-14 17:44:24
8.9K2
发布2018-10-14 17:44:24
举报
文章被收录于专栏:每周一脱topic每周一脱topic

切片slice是golang中的一种非常重要和关键的数据类型,被大量地使用。本文总结数组arrays的使用,切片slice的使用以及它的底层是如何实现的。在了解底层之后,以后slice的使用上会更胸有成竹。

一、数组arrays

golang中的切片slice其实是数组arrays的一种抽象,所以要搞懂切片slice,就要先弄明白数组arrays。

数组arrays很好理解,就是一个固定长度、固定元素类型的数组。在go中数组类型包含两层意思:长度和元素类型。因此数组[2]int和数组[3]int,这两个是不同类型。虽然元素类型相同,但是长度不同。访问某个元素,可以使用下标的方式,array[n]会访问数组第n个元素,从0开始。

代码语言:javascript
复制
var a [2]int 
var b [3]int 
b[0] = 123
a = b

因为变量a和b是不同类型,所以上面的赋值语句在编译时就报错了:

代码语言:javascript
复制
./main.go:23:4: cannot use b (type [3]int) as type [2]int in assignment
Compilation finished with exit code 2

数组不需要主动的进行初始化,相对应的0值会在声明后被赋值。在内存中[2]int就是线性排列的2个int值,所以数组访问是O(1)的时间复杂度,速度极快。

代码语言:javascript
复制
var a [2]int    // [0 0] 
var c [3]string    // [3个空字符串] 

还有一点要搞清楚,数组arrays在go中是值,而不是指针。不像c或者java,数组是指向底层数组第1个元素的指针。因此在go中你赋值或者传递数组arrays,都会对整个数组内容进行一份复制。所以为了避免无谓的复制,我们会传递数组的指针,而不是数组。

初始化声明一般有两种。e这种声明不用填长度,编译器会帮你计算这个数。

代码语言:javascript
复制
d := [2]int{11, 22} 
e := [...]int{11, 22} 
fmt.Println(d == e)  // 输出 true

二、切片slices

2.1 使用

由于数组arays的特性,在go代码中适用场景有限,而切片slices会用得非常多。切片slices基于数组,但提供了更高的灵活性。

[]T就是一个切片slices,和声明数组的区别就是没有指定长度。可以使用范围来截取切片,例如s1[x:y],会将s1中x位的元素至y-1位的元素截取。

代码语言:javascript
复制
d := [2]int{11, 22} // 数组类型 
s1 := []int{11, 22, 33} // 切片类型 
fmt.Println(s1[:2])  // [11 22]
fmt.Println(s1[1:2]) // [22]

创建一个切片会经常用到内建函数make,它的函数声明如下。我们可以看到,make可接受3个参数,第1个是切片,第2个是切片的长度,第3个是可选的容量大小。不指定cap容量的话,默认会和长度len相同。

代码语言:javascript
复制
func make([]T, len, cap) []T

2.2 slicing的底层细节

s := make([]int, 5),s底层即为上图的数据结构。ptr是一个指针,指向底层对应的数组。len是切片的长度5,cap是底层数组的容量5。

当我们执行下面语句时 :

代码语言:javascript
复制
s2 := s[1:3]

做slicing的时候,go会新建一个slice值s2,而底层的数据是不动的。s2如上图深蓝色,通过更改指针、长度和容量来进行slicing。这也就是为什么slicing的性能非常高的原因。

一个slice不能越过cap进行操作,这个我们从底层很容易理解,因为就相当于越过底层数组的上界进行非法访问了。

2.3 切片增长

一般我们会使用内部函数append来往切片slice后动态追加元素,当cap不够时,如果reslice后可以放下,那么它会reslice。例如上面的s2,底层的数组足以再追加2个元素。如果不行,那么它会new一个新的底层数组,大小为之前cap的两倍,并将之前的元素复制进去。下面测试了下:

代码语言:javascript
复制
s := make([]int, 5) 
s = append(s, 6, 7) 
fmt.Println(len(s), cap(s)) // 输出7 10 
s = append(s, 8, 9, 10, 11) 
fmt.Println(len(s), cap(s))//输出11 20 

如果要追加一个slice到另一个slice的话,这样:

代码语言:javascript
复制
s5 := make([]int, 5) 
s6 := []int{11, 22, 33, 44, 55, 66} 
s := append(s5, s6...) 
fmt.Println(s, len(s), cap(s))//输出[0 0 0 0 0 11 22 33 44 55 66] 11 12 

2.4 注意内存

在这样的场景下注意:如果我们只用到一个slice的一小部分,那么底层的整个数组也将继续保存在内存当中。当这个底层数组很大,或者这样的场景很多时,可能会造成内存急剧增加,造成崩溃。

所以在这样的场景下,我们可以将需要的分片复制到一个新的slice中去,减少内存的占用。例如一个很大的切片data里,我们需要的数据是data[m:n],那么我们创建一个新的slice变量r,将数据复制到r中返回。

代码语言:javascript
复制
mydata := data[m:n]
r := make([]int, len(mydata))
copy(r, mydata) 
return r 

三、总结

通过了解数组array和切片slice的使用和底层原理,可以更透彻的理解他们的使用场景,里面有什么坑。这对我们平时编写程序是极有裨益的,后面我会继续深入,有时间再更。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、数组arrays
  • 二、切片slices
    • 2.1 使用
      • 2.2 slicing的底层细节
        • 2.3 切片增长
          • 2.4 注意内存
          • 三、总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档