Golang遍历切片删除元素引起恐慌问题
By admin
- 3 minutes read - 442 words删除一个切片的部分元素, 告知切片操作:Golang遍历切片恐慌时删除元素
问题描述
代码( 演示代码):
package main
import (
"fmt"
)
func main() {
slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
for i, value := range slice {
if value%3 == 0 { // remove 3, 6, 9
slice = append(slice[:i], slice[i+1:]...)
}
}
fmt.Printf("%v", slice)
}
运行结果
panic: runtime error: slice bounds out of range [8:6]
goroutine 1 [running]:
main.main()
/tmp/sandbox2635969259/prog.go:11 +0x212
Program exited.
解决办法:
以下是网友想到的几种办法
1、使用goto
和标签
func main() {
slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
Label:
for i, n := range slice {
if n%3 == 0 {
slice = append(slice[:i], slice[i+1:]...)
goto Label
}
}
fmt.Printf("%v", slice)
}
它的工作原理很简单,就是不停的迭代,每次删除一个元素后,都需要从切片的开头重新迭代,太过于效率低下了,特别是当一个切片很大的情况下。
2,使用另一个变量临时存储想要的元素
func main() {
slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
dest := slice[:0]
for _, n := range slice {
if n%3 != 0 { // filter
dest = append(dest, n)
}
}
slice = dest
fmt.Printf("%v", slice)
}
这种方法有些浪费内存,一是申请变量占用内存,另外切片扩大时,底层会重新申请内存空间,并将数据迁移过去。
可以看到这种方法唯一的缺点就是需要重新申请内存。
3,从Remove elements in slice,与 len
操作:
func main() {
slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
for i := 0; i < len(slice); i++ {
if slice[i]%3 == 0 {
slice = append(slice[:i], slice[i+1:]...)
i-- // should I decrease index here?
}
}
fmt.Printf("%v", slice)
}
要选哪一个,这里看一下基准测试
与基准:
func BenchmarkRemoveSliceElementsBySlice(b *testing.B) {
for i := 0; i < b.N; i++ {
slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
dest := slice[:0]
for _, n := range slice {
if n%3 != 0 {
dest = append(dest, n)
}
}
}
}
func BenchmarkRemoveSliceElementByLen(b *testing.B) {
for i := 0; i < b.N; i++ {
slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
for i := 0; i < len(slice); i++ {
if slice[i]%3 == 0 {
slice = append(slice[:i], slice[i+1:]...)
}
}
}
}
$ go test -v -bench=".*"
testing: warning: no tests to run
PASS
BenchmarkRemoveSliceElementsBySlice-4 50000000 26.6 ns/op
BenchmarkRemoveSliceElementByLen-4 50000000 32.0 ns/op
从结果上看来使用第 2
种方法好像好一些的。
哪还有更好的解决办法吗,这里网友给出了一种更合理的解决方案( https://stackoverflow.com/questions/38387633/golang-remove-elements-when-iterating-over-slice-panics/38387701#38387701)。
package main
import "fmt"
func main() {
slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
k := 0
for _, n := range slice {
if n%3 != 0 { // filter
slice[k] = n
k++
}
}
slice = slice[:k]
fmt.Println(slice) //[1 2 4 5 7 8]
}
实现原理就是将要保留的数据向左边存储,不需要另外申请内存,原地移动数据元素( 演示代码),不得不说这个方法真是让人脑洞大开。
如果您需要新片或保留旧切片
package main
import "fmt"
func main() {
slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s2 := make([]int, len(slice))
k := 0
for _, n := range slice {
if n%3 != 0 { // filter
s2[k] = n
k++
}
}
s2 = s2[:k]
fmt.Println(s2) //[1 2 4 5 7 8]
}
参考资料
- http://cn.voidcc.com/question/p-mkbvfagj-hy.html
- https://github.com/golang/go/wiki/SliceTricks
- https://stackoverflow.com/questions/38387633/golang-remove-elements-when-iterating-over-slice-panics/38387701#38387701