# Array数组
Golang中的数组是静态的,存储着一段相同内容的连续空间。
# 基本使用
# 声明和初始化
在Go中数组的初始化主要有两种方式:一种是显示的指定数组大小,另一种是使用[...]
自动推导数组大小的方式。
|
|
以上两种方式运行结果是一样的,后一种方式会在编译期间自动推导成第一种,只是Go为我们提供的语法糖。
Go编译器在初始化数组时,会根据字面量的多少来进行不同的优化:
-
当元素的数量小于或等于4个时,会将数组的元素直接放在栈上。
-
当元素的数量大于4个时,会将数组中元素放置在静态区并在运行时取出。
# 访问和读写
数组使用arr[n]
来访问数组内元素,如果是一些简单的数组或者字符串的越界错误会在编译期间发现,而如果使用变量去访问数组,会在运行时触发程序的错误并导致崩溃退出。
# 多维数组使用
在Go多维数组的初始化和一维类似:
|
|
# Slice切片
# 定义和初始化
切片是Go提供的基于array的一种动态数组,其长度并不像数组那样固定,我们可以向切片中追加元素或者进行扩容等操作。
切片的初始化有三种方式:
-
通过下标初始化获得切片或者数组的一部分
-
使用字面量初始化新的切片
-
使用关键字make来创建切片
|
|
当Go编译器在创建切片时:
-
如果切片发生逃逸或者切片的大小或容量特别大时,需要在运行时在堆上创建底层数组和切片。
-
当切片特别小时,Go编译器会先在栈上或者静态存储区初始化数组,再通过下标(即第一种
arr[2:3]
的方式)得到切片。
在运行时创建切片时,编译器会计算切片所需要的空间并在堆上申请一片连续的内存空间(空间不足会panic):
内存大小=元素大小x切片容量
当内存分配完成,会返回底层数组的引用,并且和长度,容量合并成SliceHeader
的结构体
切片的底层数据结构如下:
|
|
可以发现切片和数组主要不同在于cap
字段。
切片实际上是在数组的基础上加了一层抽象层,切片实际上是底层数组的一个引用,再加上长度和容量,当我们在运行时修改切片的长度和容量时,底层的数组可能会发生变化,而在上层引用看来切片并没有发生变化。
切片和数组还有一点不同在于:切片只是在编译期间确定元素类型,而数组的编译期间就已经确定好了类型和长度。
# 切片的访问
切片通过下标去访问元素:
|
|
在访问时,Go会进行边界检查,如果超出则会panic。
切片可以获取长度和容量:
|
|
# 切片追加和扩容
在Go中使用append
关键字对切片进行扩容, 扩容后会产生一个新的slice结构体,如果赋值回去原变量就相当于对原变量进行了扩容。
|
|
当切片追加元素时,会根据:
-
追加后切片长度小于等于容量
-
追加后切片长度大于容量
以及根据返回值是否覆盖原切片进行不同的流程
如果触发了第二种情况,即容量不足的情况,Go会对切片进行扩容,扩容其实是为切片分配新的内存空间并拷贝原切片中元素的过程。
在分配内存空间之前需要先确定新的切片容量,运行时根据切片的当前容量选择不同的策略进行扩容:
Go 1.18之前:
- 如果期望容量大于当前容量的两倍就会使用期望容量;
- 如果当前切片的长度小于 1024 就会将容量翻倍;
- 如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;
Go 1.18之后:
切片扩容不再以1024为临界点,而是设定了一个值为256的
threshold
。在计算完容量之后,会根据容量和元素大小相乘,如果新的切片发生了内存溢出或者请求内存大于上限则会直接panic。 - 如果期望容量大于当前容量的两倍就会使用期望容量;
- 当原切片容量 < threshold 的时候,新切片容量变成原来的 2 倍;
- 当原切片容量 > threshold 的时候,进入一个循环,每次容量增加(旧容量+threshold*3) / 4;
# 切片拷贝
在Go中使用copy
关键字进行切片的复制,实际上底层使用的是对内存的复制。
Go对切片仅支持append
和copy
两种操作,需要注意的是在大切片中进行这两种操作会比较消耗资源。
# 案例
|
|