看完苏炳添进入总决赛,看得我热血沸腾的,上厕所都不敢耽搁超过 5 分钟。
这历史性的一刻,让本决定休息的我,垂死病中惊坐起,开始肝文章。
引子
bugstructLabels大家看看会出现什么问题。
for i := range m{
m[i].Labels = append(r.Config.Relabel, m[i].Labels...)
...
}
debugi=0nm[?].Labelsappendappend改为如下代码,调换下了位置,一切正常了。
m[i].Labels = append(m[i].Labels,r.Config.Relabel...)
这是一个隐含的陷阱,在 go 语言中赋值拷贝往往都是浅拷贝,开发者很容易不小心忽视这一点,导致这种无法预料的问题出现,以后要多多注意了。
借由这个问题以及的作业中,提到的深度拷贝问题展开今天的文章。
何谓浅?何谓深?
c++PythonGo浅拷贝对于值类型是完全拷贝一份,而对于引用类型是拷贝其地址。也就是拷贝的对象修改引用类型的变量同样会影响到源对象。
channelGo指针slicechannelinterfacemap函数map方便的点是作为参数传递不需要取地址可以直接修改其内容,只要函数内部不出现覆盖就不需要返回值。
但作为结构体中的成员变量,在拷贝结构体后问题就暴露出来了。修改一处导致另一处也变了。
深拷贝的四种方式
json我听到这种方案,顿时惊为天人,确实挺省事的,但由于序列化会用到反射,效率自然不会太高。
深拷贝有四种方式
jsongobgithubgithub手写拷贝函数
定义一个包含切片、字典、指针的结构体。
type Foo struct {
List []int
FooMap map[string]string
intPtr *int
}
Duplicatefunc (f *Foo) Duplicate() Foo {
var tmp = Foo{
List: make([]int, 0, len(f.List)),
FooMap: make(map[string]string),
intPtr: new(int),
}
copy(tmp.List, f.List)
for i := range f.FooMap {
tmp.FooMap[i] = f.FooMap[i]
}
if f.intPtr != nil {
*tmp.intPtr = *f.intPtr
} else {
tmp.intPtr = nil
}
return tmp
}
copymaprangemapnil测试
func main() {
var a = 1
var t1 = Foo{intPtr: &a}
t2 := t1.Duplicate()
a = 2
fmt.Println(*t1.intPtr)
fmt.Println(*t2.intPtr)
}
输出说明深拷贝成功
2
1
json序列化反序列化
这种方式完成深度拷贝非常简单,但必须结构体加上注解,而且不允许出现私有字段
type Foo struct {
List []int `json:"list"`
FooMap map[string]string `json:"foo_map"`
IntPtr *int `json:"int_ptr"`
}
提供一个直接的方案
func DeepCopyByJson(dst, src interface{}) error {
b, err := json.Marshal(src)
if err != nil {
return err
}
err = json.Unmarshal(b, dst)
return err
}
srcdstdst用法,我省略了错误处理
a = 3
t1 = Foo{IntPtr: &a}
t2 = Foo{}
_ = DeepCopyByJson(&t2, t1)
fmt.Println(*t1.IntPtr)
fmt.Println(*t2.IntPtr)
输出
3
3
gob序列化反序列化
protobufPythonpickleJavaSerialization在发送端编码,接收端解码。
func DeepCopyByGob(dst, src interface{}) error {
var buffer bytes.Buffer
if err := gob.NewEncoder(&buffer).Encode(src); err != nil {
return err
}
return gob.NewDecoder(&buffer).Decode(dst)
}
用法
a = 4
t1 = Foo{IntPtr: &a}
t2 = Foo{}
_ = DeepCopyByGob(&t2, t1)
fmt.Println(*t1.IntPtr)
fmt.Println(*t2.IntPtr)
输出
4
4
基准测试(性能测试)
go基准测试代码,这里仅写一个,其他两个函数的测试方式类似:
func BenchmarkDeepCopyByJson(b *testing.B) {
b.StopTimer()
var a = 1
var t1 = Foo{IntPtr: &a}
t2 := Foo{}
b.StartTimer()
for i := 0; i < b.N; i++ {
_ = DeepCopyByJson(&t2, t1)
}
}
运行测试
$ go test -test.bench=. -cpu=1,16 -benchtime=2s
goos: darwin
goarch: amd64
pkg: my_copy
cpu: Intel(R) Core(TM) i5-8257U CPU @ 1.40GHz
BenchmarkFoo_Duplicate 35887767 62.64 ns/op
BenchmarkFoo_Duplicate-16 37554250 62.56 ns/op
BenchmarkDeepCopyByGob 104292 22941 ns/op
BenchmarkDeepCopyByGob-16 103060 23049 ns/op
BenchmarkDeepCopyByJson 2052482 1171 ns/op
BenchmarkDeepCopyByJson-16 2057090 1175 ns/op
PASS
ok my_copy 17.166s
mac手动拷贝方式 > json > gob小结
json如果是频繁拷贝的程序,建议使用手动拷贝方式进行拷贝,而且可以定制化拷贝的过程。甚至可以完成不同结构体之间,字段细微差异的定制化需求。
copyreflect.copycopy拓展资料
往期精彩回顾
没想到文章写完还是到了第二天,原创不易,点个赞支持一下,爱你么么!
这次简单提到了基准测试,下一次我们展开详细学习单元测试与基准测试的内容,我们下次见!
希望您可以帮我点个赞,双击一下,鼓励一下我,这对我很重要,谢谢姐妹!