43 lines
3.4 KiB
Markdown
43 lines
3.4 KiB
Markdown
## 切片Slice
|
||
## 切片容量如何增长
|
||
在 Go语言中,切片的容量是一种动态增长的机制。当切片的长度达到或超过容量时,Go 语言会自动扩展其底层数组的容量,一般由 append 触发。切片容量增长(growslice)的具体规则在不同版本的规则不同。
|
||
### 对于 go1.18 之前来说:
|
||
- 如果期望容量大于当前容量的两倍就会使用期望容量;
|
||
- 如果当前切片的长度小于1024的话,growslice 时就会将容量翻倍;
|
||
- 如果当前切片的长度大于1024的话,growslice时会每次增加 **25%** 的容量,直到新容量大于期望容量。
|
||
### 对于go1.18之后来说:
|
||
减小了倍增阈值,但是在后续25%的幅度增加的时候把阈值作为基准的一部分,来避免扩容次数过多的问题:
|
||
- 如果期望容量大于当前容量的两倍就会使用期望容量;
|
||
- 如果当前切片的长度小于阈值256的话,growslice时就会将容量翻倍;
|
||
- 如果当前切片的长度大于等于256,就会每次增加`(newcap+3*threshold)/4`的容量,直到新容量大于期望容量。
|
||
此外,在扩容之后还会进行一步roundupsiz,这一步主要是靠内存对齐的优化,来计算出最终的容量。
|
||
### 切片与底层数组的关系:
|
||
- 切片本质上是一个对底层数组的抽象。一个切片包含三个部分:指向底层数组的指针、切片的长度和切片的容量。
|
||
- 切片是动态数组,它的大小并非固定不变,而是可以根据需要动态扩展。
|
||
### 切片容量增大的过程:
|
||
- 当我们向切片中追加元素,如果已有的容量不足以容纳新的元素,Go语言会自动分配一个更大的底层数组,并将旧的元素**复制**到新的数组中。
|
||
- 在容量小于1024时,新的容量至少是旧容量的两倍。而当容量达到或超过1024时,增长因子减小为1.25。
|
||
### 什么是内存分配和复制?
|
||
- 当切片的容量扩展时,Go运行时会新分配一块更大的内存空间,这涉及到内存分配操作。
|
||
- 然后,运行时将旧的底层数组的内容**复制**到新的内存空间。复制操作虽然是透明的,但是它的开销不容忽视,尤其是在大规模数组操作时。因此,为了**减少频繁的容量扩展**,通常我们可以在**初始化**切片时就指定一个合理的容量。
|
||
### Go语言优化措施:
|
||
- Go语言中的切片设计考虑了性能和灵活性,它采用了倍增和低倍增的机制来实现容量的自动成长。
|
||
- 这种设计使得切片既能在小规模数据处理时保持高效,又能在处理大规模数据时避免频繁的内存分配和数据复制开销。
|
||
|
||
## 内建函数 make 和 new 的区别是什么
|
||
首先 make 和 new 均是 Go 语言的内置的用来分配内存的函数。但是使用的类型不同:前者使用于 `slice,map,channel` 等引用类型;后者适用于 int 型、数组、结构体等值类型。
|
||
|
||
其次,两者的函数形式及调用形式不同,函数形式如下:
|
||
~~~go
|
||
func make(t Type, size ...IntegerType) Type
|
||
func new(Type) *Type
|
||
~~~
|
||
前者返回一个值,后者返回一个指针。
|
||
|
||
使用上, make 返回初始化之后的类型的引用, new 会为类型的新值分配已置零的内存空间,并返回指针。例如:
|
||
~~~ go
|
||
s := make([]int, 0, 10) // 使用 make 创建一个长度为0,容量为10的切片
|
||
a = new(int) // 使用 new 分配一个零值的 int 型
|
||
*a = 5
|
||
~~~
|