在Go语言的实际开发中,切片是最常用的数据结构之一,经常需要对其中的元素进行增删改操作。当需要从切片中删除多个元素时,如果操作不当很容易引发索引错误或者得到不符合预期的结果,因此需要掌握安全高效的处理方式。

常见的错误删除方式
很多开发者会尝试用循环遍历的方式删除多个元素,比如下面的代码:
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5, 6, 7, 8}
// 错误示例:循环删除偶数元素
for i := 0; i < len(s); i++ {
if s[i]%2 == 0 {
s = append(s[:i], s[i+1:]...)
// 删除后索引i对应的元素已经变化,会导致漏删或者越界
i--
}
}
fmt.Println(s)
}
这种方式在删除元素后调整索引,虽然能实现功能,但逻辑复杂,而且如果删除逻辑处理不当很容易出现bug,同时多次append操作也会带来额外的性能开销。
安全高效的多元素删除技巧
1. 双指针原地替换法
这种方法不需要额外创建新的切片,通过两个指针遍历切片,一个指针记录当前有效元素的位置,另一个指针遍历所有元素,将不需要删除的元素放到有效位置,最后截断切片即可,时间复杂度为O(n),空间复杂度为O(1)。
package main
import "fmt"
func removeMultiElements(s []int, needRemove func(int) bool) []int {
// 慢指针,记录有效元素的位置
slow := 0
for fast := 0; fast < len(s); fast++ {
// 如果当前元素不需要删除,就放到慢指针位置
if !needRemove(s[fast]) {
s[slow] = s[fast]
slow++
}
}
// 截断切片,保留有效元素
return s[:slow]
}
func main() {
s := []int{1, 2, 3, 4, 5, 6, 7, 8}
// 删除所有偶数元素
result := removeMultiElements(s, func(val int) bool {
return val%2 == 0
})
fmt.Println(result) // 输出 [1 3 5 7]
}
2. 新建切片过滤法
这种方法会创建一个新的切片,遍历原切片时将不需要删除的元素添加到新切片中,逻辑更直观,适合对内存开销不敏感的场景。
package main
import "fmt"
func removeMultiElementsNew(s []int, needRemove func(int) bool) []int {
// 预分配足够的容量,减少扩容次数
result := make([]int, 0, len(s))
for _, val := range s {
if !needRemove(val) {
result = append(result, val)
}
}
return result
}
func main() {
s := []int{1, 2, 3, 4, 5, 6, 7, 8}
result := removeMultiElementsNew(s, func(val int) bool {
return val%2 == 0
})
fmt.Println(result) // 输出 [1 3 5 7]
}
3. 按索引批量删除法
如果已经知道需要删除的元素的索引集合,可以先对索引进行排序,然后从后往前删除,避免删除后索引变化的问题。
package main
import (
"fmt"
"sort"
)
func removeByIndexes(s []int, indexes []int) []int {
// 对索引进行降序排序
sort.Slice(indexes, func(i, j int) bool {
return indexes[i] > indexes[j]
})
// 从后往前删除,避免索引变化
for _, idx := range indexes {
if idx < 0 || idx >= len(s) {
continue
}
s = append(s[:idx], s[idx+1:]...)
}
return s
}
func main() {
s := []int{1, 2, 3, 4, 5, 6, 7, 8}
// 删除索引为1、3、5的元素
indexes := []int{1, 3, 5}
result := removeByIndexes(s, indexes)
fmt.Println(result) // 输出 [1 3 5 7 8]
}
不同方法的适用场景
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 双指针原地替换法 | O(n) | O(1) | 需要原地修改切片,对内存开销敏感的场景 |
| 新建切片过滤法 | O(n) | O(n) | 逻辑简单优先,不需要原地修改切片的场景 |
| 按索引批量删除法 | O(n + m log m),m为删除索引数量 | O(m) | 已经明确需要删除的元素索引的场景 |
注意事项
- 如果切片的元素是指针或者引用类型,原地替换法不会主动释放被删除元素的引用,可能需要手动处理避免内存泄漏。
- 新建切片过滤法返回的是新的切片,如果原切片有其他引用,修改新切片不会影响原切片,而双指针法会修改原切片的内容。
- 删除元素后如果需要复用原切片的底层数组,优先选择双指针原地替换法,减少内存分配次数。