## 切片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 ~~~