Loading

go 语言的 slice 注意点

前言

起因是在编写某个模块时, 考虑到slice良好的伸缩性, 便使用slice来存储大量的数据, 业务是slice的数据会慢慢的弹出减少, 理想状态下内存占用会越来越少, 结果发现, 占用的内存并不会返还, 于是查了一下, 好家伙 😵‍💫, 坑还不少, 索性这里记录一下坑吧

slice

slice其实是一个指向底层数组的指针结构体, 这个结构体有三个属性, 分别是: 指向数组的指针

  • 元素的长度
  • 底层数组的长度

我们都知道, 数组是固定长度的, 而切片是动态扩容的, 所以, 切片结构有一个底层数组的长度, 使用cap()可以查看, 当发现插入后的长度大于原先的底层数组的长度时, 会进行扩容, 即将申请一个新的数组,将原来的数据放置到新的数组中, 将指向数组的指针也修改为新的指针

slice扩容的策略是:

  • 原先的数组长度 < 1024 时, 扩容后的容量为原先的2倍
  • 原先的数组长度 > 1024 时, 扩容后的容量为原先的1.25倍

数据共享问题

看以下代码

func t1() {
	a1 := []int{1, 2, 3, 4}
	a2 := a1
	a3 := a1[:3]

	a1[0] = 5

	fmt.Println(a1, a2, a3)
}

运行结果是

[5 2 3 4] [5 2 3 4] [5 2 3]

可以看到, 只修改了a1, 却造成了a2a3一起改变, 这代表此时a1/a2/a3共享了数据

我们再看代码

func t2() {
	a1 := []int{1, 2, 3, 4}
	a2 := append(a1, 6)
	a3 := a1[:3]

	a1[0] = 5

	fmt.Println(a1, a2, a3)
}

结果是

[5 2 3 4] [1 2 3 4 6] [5 2 3]

这里的a1/a3共享了数据, a2就单独使用了, 这是因为append()会面临内存的重新分配问题, 在a2进行append()时候, 重新申请了内存空间, 将a1指向的数据拷贝一份, 然后增加了新值5, 放入了新的地址中, 此时a2的地址与a1/a3不一样, 所以对a1进行修改只影响到了a1/a2

再看代码

func t3() {
	a1 := []int{1, 2, 3, 4}
  a2 := append(a1, 6)
	a3 := a1[:1]
	fmt.Println(cap(a3))
	a3 = append(a3, 6)
	fmt.Println(cap(a3))

	a1[0] = 5

	fmt.Println(a1, a2, a3)
}

运行结果

4
4
[5 6 3 4] [1 2 3 4 6] [5 6]

我们这里发现了两个疑点

a3经过append()还是与a1共享数据

还记得最开始提到的切片扩容机制吗? 没错, 对于append(), 在执行时也会有两种分支区别, 只有容量不够才会进行重新分配, 步骤与普通的切片扩容一致

如果容量够, 比如这里, a3的容量与a1一致是4, 而a3长度由1增加到2, 没有超过4, 因此没有进行内存的重新分配, 导致a3/a1继续共享数据

a2append()时, 长度变成5, 原有的容量不够, 因此进行了内存的重新分配

a3进行append()a1的中间进行了数值插入

因为a3是截取的a1[:1], 所以对a3进行append()在不重新分配内存的时候就会在a[0]后添加数据

变量逃逸问题

因为存在变量共享的问题, 那么变量逃逸也就出现了

举个例子

func t5() {
	a1 := []int{1, 2, 3, 4, 5, 6}
	b1 := a1[:2]
	// a1 不再使用
	fmt.Println(b1)
}

假如a1很大, 占用很多内存, 此时你只想获取a1的某一段数据, 而a1以后不再使用, 因为变量共享, 此时就会导致 GC 时a1的长切片不会被回收, 仍然存在与内存中, 如果这个函数一直运行, 这将是一个很大的隐患

正确做法是, 在明确a1不使用时, 将a1赋值成nil, 例如

func t5() {
	a1 := []int{1, 2, 3, 4, 5, 6}
	b1 := []int{}              // 创建一个空切片
	b1 = append(b1, a1[:2]...) // 通过 append 添加需要的元素
	a1 = nil                   // 将 a1 设置为 nil
	// a1 不再使用
	fmt.Println(b1, cap(b1))
}

运行结果为

[1 2] 2
posted @ 2022-09-30 10:53  ChnMig  阅读(92)  评论(0编辑  收藏  举报