如何在 Go 中高效反转切片

go 1.21+ 可直接使用内置的 `slices.reverse`;旧版本需手动循环、泛型函数或反射实现,所有方法均支持原地反转,如需保留原切片,应先调用 `slices.clone` 复制。

在 Go 中反转切片是一个高频操作,但标准库在不同版本中提供了差异化的支持方案。自 Go 1.21 起,slices 包(位于 golang.org/x/exp/slices 的功能已正式并入标准库 slices)新增了通用、安全且高效的 Reverse 函数,适用于任意可索引切片类型(如 []int、[]string、[]struct{} 等),无需类型断言或反射开销。

推荐方式(Go ≥ 1.21):

import "slices"

s := []int{1, 2, 3, 4, 5}
slices.Reverse(s) // 原地反转 → [5 4 3 2 1]

该函数是泛型实现,类型安全、零分配、性能最优,且已通过充分测试,是当前首选方案。

⚠️ 兼容旧版本(Go

  • 手动双指针循环(最通用、无依赖、高性能):
    适用于所有 Go 版本,清晰易懂,无额外开销:

    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
  • 泛型函数(Go ≥ 1.18,类型安全首选):
    封装为可复用工具函数,支持任意切片类型:

    func Reverse[S ~[]E, E any](s S) {
        for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
            s[i], s[j] = s[j], s[i]
        }
    }
    
    // 使用示例
    nums := []int{10, 20, 30}
    Reverse(nums) // → [30 20 10]
  • 反射方案(Go ≥ 1.8,慎用):
    可处理任意切片类型(包括未导出字段结构体切片),但性能较差、失去类型检查,仅作兜底:

    import "reflect"
    
    func ReverseAny(s interface{}) {
        v := reflect.ValueOf(s)
        if v.Kind() != reflect.Slice {
            panic("ReverseAny: given value is not a slice")
        }
        n := v.Len()
        swap := reflect.Swapper(s)
        for i, j := 0, n-1; i < j; i, j = i+1, j-1 {
            swap(i, j)
        }
    }

? 重要注意事项:

  • 所有上述方法均为 原地(in-place)反转,会直接修改原始切片底层数组。若需保留原切片,请先克隆:
    reversed := slices.Clone(original)
    slices.Reverse(reversed)
  • slices.Clone(Go 1.21+)或 append([]T(nil), s...)(旧版)可用于深拷贝切片数据(注意:仅浅拷贝元素值,对指针/结构体字段不递归复制)。
  • 避免误用 sort.Reverse(sort.Interface):它用于排序包装器,不能直接反转切片,且需实现 Len/Less/Swap,复杂度高且不适用。

总结:优先升级至 Go 1.21+ 并使用 slices.Reverse;若需兼容旧版本,推荐泛型 Reverse 函数——兼顾安全性、可读性与性能;反射方案仅在极端动态场景下考虑,并务必添加运行时类型校验。