Go反射可用但需规避热路径调用,应缓存Type/字段索引、用代码生成替代、优先接口断言,仅在配置解析等必要场景初始化时使用反射并缓存元数据。
Go反射不是不能用,而是不能在热路径里反复调用——一次 reflect.ValueOf 的开销就比直接赋值高50%,字段访问慢20倍,方法调用慢40倍。
缓存 reflect.Type 和字段索引,别每次重新扫描结构体
每次调用 reflect.TypeOf 或遍历 t.NumField() 都会重新解析结构体元数据,纯属CPU浪费。真实场景中(比如首次解析 User 结构体),你只需要做一次;后续所有同类型对象都该复用结果。
- 用
sync.Map缓存reflect.Type→ 字段名/标签/偏移量映射,key 可以是t.String()或unsafe.Pointer(t) - 预提取字段索引(
structField.Index)和 JSON/db tag,避免每次field.Tag.Get("json") - 不要缓存
reflect.Value实例本身——除非对象是只读且生命周期可控;否则容易因底层值变更导致行为不一致
用代码生成替代运行时反射,尤其在序列化、ORM、参数绑定场景
像 encoding/json 包内部其实也尽量规避反射;真正高频的字段操作,编译期生成专属代码才是零开销方案。
- 对结构体字段操作(如转 map、校验、数据库插入),优先用
go:generate+ 模板生成MarshalJSON/Scan方法 - 常用工具:
ent(ORM)、sqlboiler(DB 绑定)、protoc-gen-go(protobuf 序列化) - 自定义脚本示例:扫描
type User struct { Name string `json:"name"` },生成func (u *User) GetJSONKey(k string) interface{},绕过所有反射查找
用接口断言代替 reflect.Value.Kind() 做类型分发
很多人一想“要根据不同类型走不同逻辑”,第一反应就是 reflect.TypeOf(v).Kind(),其实这既慢又失去类型安全。