3.4 KiB
3.4 KiB
切片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 型、数组、结构体等值类型。
其次,两者的函数形式及调用形式不同,函数形式如下:
func make(t Type, size ...IntegerType) Type
func new(Type) *Type
前者返回一个值,后者返回一个指针。
使用上, make 返回初始化之后的类型的引用, new 会为类型的新值分配已置零的内存空间,并返回指针。例如:
s := make([]int, 0, 10) // 使用 make 创建一个长度为0,容量为10的切片
a = new(int) // 使用 new 分配一个零值的 int 型
*a = 5