两种翻转slice的方式对比
刷题时,遇到一个需求如下:对一个slice进行翻转。
在实现的过程使用了两种方式:
- 每次插入新元素时,使用append左插入的方式
- 先正常append右插入,最后再对这个slice进行翻转
两种方式的实现及时间和空间消耗的对比
实现
下面两个方法实现的功能是一致的
方法1
使用左插入法来实现翻转,如下所示1
2
3
4
5
6
7
8
9
10
11
12func method1() {
result := make([][]int, 0)
for i := 0; i < 100; i++ {
item := make([]int, 0)
for j := 0; j < 10; j++ {
item = append(item, i*j)
}
// 左插入
join := [][]int{item}
result = append(join, result...)
}
}方法2
先正常append,再翻转1
2
3
4
5
6
7
8
9
10
11
12
13
14
15func method2() {
result := make([][]int, 0)
for i := 0; i < 100; i++ {
item := make([]int, 0)
for j := 0; j < 10; j++ {
item = append(item, i*j)
}
result = append(result, item)
}
tmp := result
result = make([][]int, 0)
for i := len(tmp) - 1; i >= 0; i-- {
result = append(result, tmp[i])
}
}
性能对比
时间
1
2
3
4goos: darwin
goarch: amd64
BenchmarkMethod1-4 17006 69558 ns/op
BenchmarkMethod2-4 43002 27851 ns/op时间上,方法2要好于方法1
空间
空间上,方法2也要优于方法1
分析
通过pprof工具对其源码进行分析如下
从上图我们发现,result = append(join, result...)
语句的内存消耗非常严重,
同时这种方法进行append,会使得地址重新分配(因为首地址改变了)致使多余内存和时间的消耗。
补充
方法2的翻转可以有两种方法实现
- 如上面所示,通过slice反向遍历插入完成翻转
- 还可以通过双指针法来翻转
这里通过一个简单例子比较下双方的性能
1 | func method1() { |
结果表示,双指针法会更好一些,理由很简单,因为双指针是O(N/2)的
1 | $ go test -run=. -bench=. -benchmem |
结论
这种情况下推荐使用方法2,同时对于方法1(左插入)的使用要格外谨慎,不当的使用会使得程序的时/空间消耗加剧。
如果有更好的实现方式,或者对上述程序有更好的建议,欢迎下方评论~