文章(原文地址:https://philpearl.github.io/post/aintnecessarilyslow/)非常有学习价值,故翻译整理了下来。
不要使用反射,除非你真的需要。但是当你不使用反射时,不要认为这是因为反射很慢,它也可以很快。
反射允许你在运行时获得有关 Go 类型的信息。如果你曾经愚蠢地尝试编写 json.Unmarshal 之类的新版本,本文将探讨的就是如何使用反射来填充结构体值。
切入点案例
我们以一个简单的案例为切入点,定义一个结构体 SimpleStruct,它包括两个 int 类型字段 A 和 B。
type SimpleStruct struct {
A int
B int
}
假如我们接收到了 JSON 数据 {"B": 42},想要对其进行解析并且将字段 B 设置为 42。
在下文,我们将编写一些函数来实现这一点,它们都会将 B 设置为 42。
如果我们的代码只适用于 SimpleStruct,这完全是不值一提的。
func populateStruct(in *SimpleStruct) {
in.B = 42
}
反射基本版
但是,如果我们是要做一个 JSON 解析器,这意味着我们并不能提前知道结构类型。我们的解析器代码需要接收任何类型的数据。
在 Go 中,这通常意味着需要采用 interface{} (空接口)参数。然后我们可以使用 reflect 包检查通过空接口参数传入的值,检查它是否是指向结构体的指针,找到字段 B 并用我们的值填充它。
代码将如下所示。
func populateStructReflect(in interface{}) error {
val := reflect.ValueOf(in)
if val.Type().Kind() != reflect.Ptr {
return fmt.Errorf("you must pass in a pointer")
}
elmv := val.Elem()
if elmv.Type().Kind() != reflect.Struct {
return fmt.Errorf("you must pass in a pointer to a struct")
}
fval := elmv.FieldByName("B")
fval.SetInt(42)
return nil
}
让我们通过基准测试看看它有多快。
func BenchmarkPopulateReflect(b *testing.B) {
b.ReportAllocs()
var m SimpleStruct
for i := 0; i < b.N; i++ {
if err := populateStructReflect(&am