note4cs/技术/Go/数据容器.md
2025-03-19 14:36:09 +08:00

3.4 KiB
Raw Blame History

切片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 语言的内置的用来分配内存的函数。但是使用的类型不同:前者使用于 slicemapchannel 等引用类型;后者适用于 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