GO 进阶源码分析之 切片扩容原理
2023-04-10 18:47:17
Go 切片扩容机制剖析
什么是切片扩容?
Go 中的切片是一种轻量级的动态数组,当切片的长度超过其容量时,需要进行扩容才能存储更多元素。扩容是指分配一个新切片,其容量比原切片更大,并将原切片中的数据复制到新切片中。
扩容机制演变
Go 语言中切片的扩容机制随着版本的更新而不断演变。在 Go 1.18 版本之前和之后,切片的扩容行为存在差异。
Go 1.18 之前的扩容机制
在此版本中,切片的扩容行为如下:
- 切片容量小于 1024 时,扩容时容量会翻倍。
- 切片容量大于或等于 1024 时,扩容时容量会增加 1.25 倍。
- 扩容后,切片的容量会向上取整到最近的 2 的幂。
Go 1.18 之后的扩容机制
从 Go 1.18 版本开始,切片的扩容行为有所改变:
- 切片容量小于 1024 时,扩容时容量会翻倍。
- 切片容量大于或等于 1024 时,扩容时容量会增加 2 倍。
- 扩容后,切片的容量会向上取整到最近的 2 的幂。
扩容源码分析
Go 1.18 之前
func growslice(et *_type, old []T, cap int) (array unsafe.Pointer) {
if cap < 1024 {
newcap := oldcap * 2 // double old cap
if newcap < cap {
newcap = cap
}
} else {
newcap = oldcap * 5 / 4 // add 25%, never less than 1024
if newcap < cap {
newcap = cap
}
}
return newarray(et, newcap)
}
从代码中可以看到,当切片容量小于 1024 时,扩容时容量会翻倍。当切片容量大于或等于 1024 时,扩容时容量会增加 1.25 倍。
Go 1.18 之后
func growslice(et *_type, old []T, cap int) (array unsafe.Pointer) {
if cap < 1024 {
newcap := oldcap * 2 // double old cap
if newcap < cap {
newcap = cap
}
} else {
newcap = oldcap * 2 // double old cap
if newcap < cap {
newcap = cap
}
}
return newarray(et, newcap)
}
从代码中可以看到,在 Go 1.18 版本中,当切片容量小于 1024 时,扩容时容量会翻倍。当切片容量大于或等于 1024 时,扩容时容量会增加 2 倍。
注意事项
使用 Go 切片时需要注意以下几点:
- 扩容可能会导致数据复制,因此避免频繁的扩容操作。
- 扩容后,原切片的数据仍然存在,只是容量变大。
- 扩容后,原切片的长度不会改变。
最佳实践
为了避免频繁的切片扩容操作,可以考虑以下最佳实践:
- 预分配切片的容量,尤其是在知道切片需要存储的数据量时。
- 当需要多次追加元素到切片时,预分配切片的容量。
- 当需要对切片进行频繁的排序或搜索操作时,预分配切片的容量。
常见问题解答
-
扩容操作的开销是什么?
扩容操作涉及分配新切片、复制数据和释放旧切片,因此会产生一些开销。 -
为什么切片的容量是 2 的幂?
2 的幂可以简化容量计算和内存管理,提高切片的性能。 -
如何检测切片是否需要扩容?
当切片的长度超过其容量时,切片需要扩容。可以通过比较切片的len
和cap
属性来检测是否需要扩容。 -
扩容后旧切片中的数据是否会被释放?
旧切片中的数据不会被释放,直到旧切片不再被引用。 -
如何减少切片的扩容次数?
通过预分配切片的容量,避免频繁的追加元素操作,并使用切片池等技术可以减少切片的扩容次数。
结论
切片的扩容机制是 Go 中一项重要的特性,它允许切片动态调整其容量以适应不断变化的数据需求。了解切片的扩容机制有助于优化代码性能,避免不必要的开销。通过遵循最佳实践和避免频繁的扩容操作,可以充分利用 Go 切片的强大功能。