秒懂 Go 数组和切片的内部实现原理

阅读数:98 2019 年 9 月 24 日 14:32

秒懂Go数组和切片的内部实现原理

很多人对 Go 语言的 array 和 slice 傻傻分不清楚,今天我们就从底层出发,来聊聊它俩到底有什么区别。

数组

几乎所有计算机语言,数组的实现都是相似的:一段连续的内存,Go 语言也一样,Go 语言的数组底层实现就是一段连续的内存空间。每个元素有唯一一个索引 (或者叫下标) 来访问。如下图所示,下图是 [5]int{1:10, 2:20}数组的内部实现逻辑图:

秒懂Go数组和切片的内部实现原理

由于内存连续,CPU 很容易计算索引 (即数组的下标),可以快速迭代数组里的所有元素。
Go 语言的数组不同于 C 语言或者其他语言的数组,C 语言的数组变量是指向数组第一个元素的指针;而 Go 语言的数组是一个值,Go 语言中的数组是值类型,一个数组变量就表示着整个数组,意味着 Go 语言的数组在传递的时候,传递的是原数组的拷贝。你可以理解为 Go 语言的数组是一种有序的 struct

slice

切片是一个很小的对象,是对数组进行了抽象,并提供相关的操作方法。切片有三个属性字段:长度、容量和指向数组的指针。

秒懂Go数组和切片的内部实现原理

上图中,ptr 指的是指向 array 的 pointer,len 是指切片的长度, cap 指的是切片的容量。现在,我想你对数组和切片有了一个本质的认识。

切片有多种声明方式,每种初始化方式对应的逻辑图是怎样的呢?

1)对于 s := make([]byte, 5) 和 s := []byte{…}的方式

秒懂Go数组和切片的内部实现原理

2)对于 s = s[2:4] 的方式

秒懂Go数组和切片的内部实现原理

3)对于 nil 的切片即 var s []byte 对应的逻辑图

秒懂Go数组和切片的内部实现原理

在此有一个说明:nil 切片和空切片是不太一样的,空切片即 s := make([]byte, 0) 或者 s := []byte{}出来的切片

空切片的逻辑图为:

秒懂Go数组和切片的内部实现原理

空切片指针不为 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}

输出:

复制代码
[10 20]

答对了吗?

作者介绍:
作者沙加(企业代号名),目前负责贝壳找房运维开发方向的相关工作。

本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。

原文链接:

https://mp.weixin.qq.com/s/r6Z7Zk-OHirmGRXXwNQbYw

评论

发布