Golang中修改原始map的副本导致原map被改变的问题
Golang中修改原始map的副本导致原map被改变的问题 我提供了一个Go Playground示例:
package main
import "fmt"
type Service struct {
name string
namespace string
endpoints []string
mappings map[string]string
}
func main() {
svc := Service{
name: "nginx",
namespace: "default",
endpoints: []string{
"192.168.0.1",
"192.168.0.2",
},
mappings: map[string]string{
"pod-0": "192.168.0.1",
"pod-1": "192.168.0.2",
},
}
nginx := svc
nginx.namespace = "ingress"
nginx.endpoints = append(nginx.endpoints, "192.168.0.40")
nginx.mappings["pod-2"] = "192.168.0.40"
fmt.Printf("updated: %+v\noriginal: %+v\n", nginx, svc)
}
我创建了一个 svc 的副本 nginx,但当我修改这个副本的 mapping 部分时,我注意到原始项 svc 中的 mapping 也被改变了!
我猜测原因是 nginx 复制了 svc 中使用的 mapping 的 地址,而不是 值。
我需要:
- 理解这是如何以及为何发生的
- 如何避免这种情况,并确保更改副本/克隆不会影响原始对象
更多关于Golang中修改原始map的副本导致原map被改变的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
3 回复
可以将 map 想象成这样:
typeap struct {
data *mapData
}
type mapData struct {
// contains filtered or unexported fields
}
当你复制一个 map 时,它确实是一个副本。只是它包含一个指针,就像复制一个切片仍然引用相同的元素一样。
如果你想将 Service 的 mappings 复制到它自己的 map 中,那么你需要编写自己的 Copy 或 Clone 函数。也许下面这样可行:
type Service struct {
name string
namespace string
endpoints []string
mappings map[string]string
}
func (s *Service) Clone() *Service {
s2 := *s // "future-proofing" in case addl. fields added.
s2.endpoints = make([]string, len(s.endpoints))
copy(s2.endpoints, s.endpoints)
s2.mappings = make(map[string]string, len(s.mappings))
for k, v := range s.mappings {
s2.mappings[k] = v
}
return &s2
这是一个典型的浅拷贝问题。在Go中,当你将一个结构体赋值给另一个变量时,会进行值拷贝,但对于引用类型字段(如slice、map、指针等),拷贝的是引用而不是底层数据。
问题分析
nginx := svc // 这是浅拷贝
这行代码执行了以下操作:
- 基本类型字段(name、namespace)被完整拷贝
- 切片字段(endpoints)拷贝了切片头(指针、长度、容量),但底层数组共享
- 映射字段(mappings)拷贝了映射描述符,但底层哈希表共享
解决方案
1. 手动深拷贝(推荐)
func deepCopyService(s Service) Service {
// 拷贝切片
endpointsCopy := make([]string, len(s.endpoints))
copy(endpointsCopy, s.endpoints)
// 拷贝映射
mappingsCopy := make(map[string]string, len(s.mappings))
for k, v := range s.mappings {
mappingsCopy[k] = v
}
return Service{
name: s.name,
namespace: s.namespace,
endpoints: endpointsCopy,
mappings: mappingsCopy,
}
}
func main() {
svc := Service{
name: "nginx",
namespace: "default",
endpoints: []string{"192.168.0.1", "192.168.0.2"},
mappings: map[string]string{"pod-0": "192.168.0.1", "pod-1": "192.168.0.2"},
}
nginx := deepCopyService(svc)
nginx.namespace = "ingress"
nginx.endpoints = append(nginx.endpoints, "192.168.0.40")
nginx.mappings["pod-2"] = "192.168.0.40"
fmt.Printf("updated: %+v\noriginal: %+v\n", nginx, svc)
}
2. 使用序列化/反序列化
import "encoding/json"
func deepCopyViaJSON(s Service) (Service, error) {
var copy Service
data, err := json.Marshal(s)
if err != nil {
return Service{}, err
}
err = json.Unmarshal(data, ©)
return copy, err
}
// 使用示例
nginx, err := deepCopyViaJSON(svc)
if err != nil {
// 处理错误
}
3. 使用第三方库
import "github.com/jinzhu/copier"
func main() {
svc := Service{
name: "nginx",
namespace: "default",
endpoints: []string{"192.168.0.1", "192.168.0.2"},
mappings: map[string]string{"pod-0": "192.168.0.1", "pod-1": "192.168.0.2"},
}
var nginx Service
// 使用DeepCopy选项
copier.CopyWithOption(&nginx, &svc, copier.Option{DeepCopy: true})
nginx.namespace = "ingress"
nginx.endpoints = append(nginx.endpoints, "192.168.0.40")
nginx.mappings["pod-2"] = "192.168.0.40"
fmt.Printf("updated: %+v\noriginal: %+v\n", nginx, svc)
}
4. 为结构体实现Clone方法
func (s Service) Clone() Service {
endpoints := make([]string, len(s.endpoints))
copy(endpoints, s.endpoints)
mappings := make(map[string]string, len(s.mappings))
for k, v := range s.mappings {
mappings[k] = v
}
return Service{
name: s.name,
namespace: s.namespace,
endpoints: endpoints,
mappings: mappings,
}
}
// 使用示例
nginx := svc.Clone()
关键点
- 切片:
make([]T, len(src))+copy()创建新切片 - 映射:
make(map[K]V, len(src))+ 循环拷贝键值对 - 性能考虑:对于大型结构体,深拷贝有性能开销
- 嵌套结构:如果结构体包含嵌套的引用类型,需要递归处理
在你的具体场景中,手动实现深拷贝是最清晰和高效的方式。

