前言
这是Go十大常见谬误系列的第6篇:slice初始化常犯的谬误。素材来源于Go布道者,现Docker公司资深工程师Teiva Harsanyi。
本文波及的源代码全副开源在:Go十大常见谬误源代码,欢送大家关注公众号,及时获取本系列最新更新。
场景
假如咱们晓得要创立的slice的长度,你会怎么创立和初始化这个slice?
比方咱们定义了一个构造体叫Bar
,当初要创立一个slice,外面的元素就是Bar
类型,而且该slice的长度是已知的。
办法1
有的人可能这么来做,先定义slice
var bars []Barbars := make([]Bar, 0)
每次要往bars
这个slice插入元素的时候,通过append来操作
bars = append(bars, barElement)
slice
实际上是一个构造体类型,蕴含3个字段,别离是
- array: 是指针,指向一个数组,切片的数据理论都存储在这个数组里。
- len: 切片的长度。
- cap: 切片的容量,示意切片以后最多能够存储多少个元素,如果超过了现有容量会主动扩容。
slice底层的数据结构定义如下:
type slice struct { array unsafe.Pointer len int cap int}type Pointer *ArbitraryType
如果依照下面的示例先创立一个长度为0的slice,那在append插入元素的过程中,bars
这个slice会做主动扩容。
如果bars
的长度比拟大,可能会产生屡次扩容。每次扩容都要创立一个新的内存空间,而后把旧内存空间的数据拷贝过去,效率比拟低。
办法2
在定义slice的时候指定长度,代码示例如下:
func convert(foos []Foo) []Bar { bars := make([]Bar, len(foos)) for i, foo := range foos { bars[i] = fooToBar(foo) } return bars}
这行代码 bars := make([]Bar, len(foos))
间接指定了slice的长度,无需扩容。
办法3
在定义slice的时候提前指定容量,长度设置为0,代码示例如下:
func convert(foos []Foo) []Bar { bars := make([]Bar, 0, len(foos)) for _, foo := range foos { bars = append(bars, fooToBar(foo)) } return bars}
这种办法也能够,也无需扩容。
那办法2和办法3哪种好一点呢?其实各有优缺点:
- 从效率上来说,办法2比办法3要高一点,因为办法3里调用了append函数,再对bars赋值,效率比间接通过
bars[i] = fooToBar(foo)
要低一点。 - 从代码的可维护性来说,办法2不能通过append函数来插入元素,因为办法2里的slice定义的时候指定了长度,如果调用append,会扩容,往现有元素前面追加元素。办法3不能通过
bars[i] =
的形式来赋值,因为办法3里的slice定义的时候长度为0,如果应用bars[1]=
,会触发panic: runtime error: index out of range [1] with length 0
。
最初强烈推荐大家看看这道对于Go slice的面试题:Go Quiz: 从Go面试题看slice的底层原理和注意事项。
看完后你会彻底理解Go slice的原理和注意事项。
举荐浏览
- Go十大常见谬误第1篇:未知枚举值
- Go十大常见谬误第2篇:benchmark性能测试的坑
- Go十大常见谬误第3篇:go指针的性能问题和内存逃逸
- Go十大常见谬误第4篇:break操作的注意事项
- Go十大常见谬误第5篇:slice初始化常犯的谬误
- Go面试题系列,看看你会几题?
开源地址
文章和示例代码开源在GitHub: Go语言高级、中级和高级教程。
公众号:coding进阶。关注公众号能够获取最新Go面试题和技术栈。
集体网站:Jincheng's Blog。
知乎:无忌。
福利
我为大家整顿了一份后端开发学习材料礼包,蕴含编程语言入门到进阶常识(Go、C++、Python)、后端开发技术栈、面试题等。
关注公众号「coding进阶」,发送音讯 backend 支付材料礼包,这份材料会不定期更新,退出我感觉有价值的材料。还能够发送音讯「进群」,和同行一起交流学习,答疑解惑。
References
- https://itnext.io/the-top-10-...