

新闻资讯
技术教程deepCopy 函数需分别处理指针、切片、映射和接口类型:指针需解引用后递归拷贝并新建指针;切片需创建新底层数组并逐元素拷贝;映射需新建并逐键值对递归拷贝;接口需先 Elem() 获取内部值,再判空避免 panic。
Go 的 reflect 包默认跳过非导出(小写开头)字段,且对指针类型不做自动解引用。直接用 reflect.Copy 或遍历 reflect.Value 字段复制时,若源是 *T、目标是 T,或结构体含私有字段,会静默失败或 panic。
reflect.Indirect 解引用到可读取的实际值,避免 panic: reflect: call of reflect.Value.Interface on zero Value
reflect.Value.Addr() 可调用),否则无法写入;常见错误是传入字面量或只读副本reflect.StructField.IsExported() 显式跳过,不能依赖 CanInterface() 判断——它在字段不可导出时直接返回 false,但不报错通用拷贝不是简单递归调用 reflect.Copy。不同复合类型的复制逻辑差异大:slice 需新建底层数组,map 要重新 make 并逐键赋值,interface{} 值需先取出具体类型再处理。
reflect.Slice:用 reflect.MakeSlice(t, v.Len(), v.Cap()) 创建新 slice,再循环 SetMapIndex 或 Index(i).Set()
reflect.Map:必须先 reflect.MakeMapWithSize(t, v.Len()),再遍历 v.MapKeys(),对每个 key/value 递归 deepCopy 后 SetMapIndex
reflect.Interface:用 v.Elem() 获取内部值,再判断其 kind 是否为 reflect.Invalid(nil interface),避免 panicfunc deepCopy(v reflect.Value) reflect.Value {
if !v.IsValid() {
return v
}
switch v.Kind() {
case reflect.Ptr:
if v.IsNil() {
return reflect.Zero(v.Type())
}
unpacked := reflect.New(v.Elem().Type())
unpacked.Elem().Set(deepCopy(v.Elem()))
return unpacked
case reflect.Slice:
if v.IsNil() {
return reflect.Zero(v.Type())
}
newSlice := reflect.MakeSlice(v.Type(), v.Len(), v.Cap())
for i := 0; i < v.Len(); i++ {
newSlice.Index(i).Set(deepCopy(v.Index(i)))
}
return newSlice
case reflect.Map:
if v.IsNil() {
return reflect.Zero(v.Type())
}
newMap := reflect.MakeMapWithSize(v.Type(), v.Len())
for _, key := range v.MapKeys() {
newMap.SetMapIndex(key, deepCopy(v.MapIndex(key)))
}
return newMap
default:
if v.CanInterface() {
return reflect.ValueOf(v.Interface())
}
return reflect.Zero(v.Type())
}
}
生产环境里常需要跳过某些字段(如数据库 ID、时间戳),或按需深拷贝(如嵌套 struct 是否展开)。靠反射自动推断不够可靠,应支持结构体字段 tag,例如 json:"-" 或自定义 copy:"-" / copy:"shallow"。
sf.Tag.Get("copy"),若为 "-" 则跳过该字段Get 返回空导致误判;不要用 sf.Tag != "" 判断是否有 tag"shallow",则直接 Set 而非递归 deepCopy,减少开销反射拷贝比手写 Clone() 方法慢 10–100 倍,GC 压力也更大。尤其在高频调用(如 HTTP 中间件、序列化循环)中,容易成为瓶颈。
go:generate + goderive)比运行时反射更稳更快,适合核心模型unsafe.Copy(Go 1.20+)或 bytes.Copy 配合 unsafe.Slice 更直接真正难的不是写通反射逻辑,而是判断什么时候不该用它——比如字段带 mutex、channel、function 或 unsafe.Pointer,这些类型根本不能也不该被反射拷贝。