json.Marshal 和 json.Unmarshal 慢因标准库依赖反射动态检查字段,导致高并发下CPU和内存开销大;easyjson通过代码生成绕过反射,配合结构体强约定、避免interface{}/map[string]interface{}、复用缓冲区等可显著提升性能。

为什么 json.Marshal 和 json.Unmarshal 会慢?
Go 标准库的 encoding/json 包在运行时大量依赖反射(reflect),每次序列化/反序列化都要动态检查结构体字段名、标签、可导出性、嵌套关系等。这意味着:字段越多、嵌套越深、类型越杂(如 interface{}、map[string]interface{}),性能损耗越明显。尤其在高并发 API 场景下,CPU 时间常被反射和内存分配吃掉大半。
用 easyjson 替代标准 json 包(零反射)
easyjson 是最成熟、兼容性最好的零反射 JSON 库之一。它不运行时解析结构体,而是通过代码生成器提前把 MarshalJSON / UnmarshalJSON 方法写死到源码里,彻底绕过 reflect。
- 安装:
go install github.com/mailru/easyjson/...@latest
- 为结构体生成方法:
easyjson -all user.go
(会在同目录生成user_easyjson.go) - 使用时直接调用生成的方法:
user.MarshalJSON()和easyjson.UnmarshalFromReader(r, &u),而非json.Marshal - 注意:生成代码依赖
easyjson运行时,需在项目中import "github.com/mailru/easyjson/json"
避免 interface{} 和 map[string]interface{} 的泛型解码
这类类型强制 json 包走最通用、最慢的路径——所有键值对都转成 map[string]interface{},再逐层递归构建,同时伴随大量 interface{} 类型断言和内存分配。
- 明确结构体定义,哪怕字段是可选的,也优先用指针字段(
*string、*int64)代替interface{} - 若必须处理未知字段,用
json.RawMessage延迟解析:type Event struct { Type string `json:"type"` Data json.RawMessage `json:"data"` }后续按Type分支再调用具体结构体的UnmarshalJSON - 禁止在 hot path 上对大 payload 做
json.Unmarshal([]byte, &map[string]interface{})
复用 bytes.Buffer 和预分配切片减少 GC 压力
频繁调用 json.Marshal 会产生大量临时 []byte,触发 GC;而 json.Encoder / json.Decoder 默认包装 io.Reader/io.Writer,底层也会反复分配缓冲区。
立即学习“go语言免费学习笔记(深入)”;
- 对高频小对象,预先分配足够大的
bytes.Buffer并复用:var buf bytes.Buffer buf.Grow(1024) err := json.NewEncoder(&buf).Encode(obj) // 使用 buf.Bytes(),之后调用 buf.Reset()
- 若已知结构体最大 JSON 长度(如日志结构体固定 200 字节内),可预分配
[]byte并传给自定义MarshalJSON方法 - 避免在循环中无脑调用
json.Marshal—— 把多次序列化合并进一个Encoder流更省
真正影响 JSON 性能的从来不是算法本身,而是反射开销、内存分配节奏和类型不确定性。生成式方案(如 easyjson)和结构体强约定,比任何运行时调优都管用。但要注意:一旦结构体变更,必须重新生成代码,CI 中漏掉这步会导致静默降级回标准包。











