json.Unmarshal vs NewDecoder.Decode:Go 中哪个更快?

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

两个杂技兄弟:Unmarshal 和 Decode



如果您使用 Go 编程并处理 JSON,那么迟早会面临选择:使用 json.Unmarshal 还是 json.NewDecoder.Decode?这个问题非常流行,以至于在 Stack Overflow 上它获得了数千次浏览和数十次投票。但您在那里找到的答案通常归结为“取决于任务”。



今天,我们不仅仅给出一个抽象的回答。我们将进行自己的调查:剖析这两种方法的内部机制,编写基准测试,展示真实的代码示例,并得出何时使用哪种方法的明确规则。系好安全带——会很精彩!



简要说明:什么是 Unmarshal 和 Decode?



这两个工具做的是同一件事——将 JSON 转换为 Go 结构体。但它们的做法不同。



  • json.Unmarshal — 接收 []byte 和一个指向结构体的指针。一步到位:字节有了,结构体准备好了,开始吧。它“直接”工作——在内存中反序列化整个数据数组。
  • json.NewDecoder.Decode — 处理流(io.Reader)。您创建一个解码器,向其传递一个读取器(例如,os.File 或 http.Response.Body),然后调用 Decode。解码器分块读取数据,不会立即将所有内容加载到内存中。


看起来区别很明显:对于大数据使用 Decoder,对于小数据使用 Unmarshal。但魔鬼总是在细节中。



内部机制:它们在底层是如何工作的



json.Unmarshal — 精准射击



Unmarshal 是一个函数,它:

  1. 接收一个字节切片。
  2. 为结果结构体分配内存。
  3. 一次性解析整个 JSON 并填充结构体。


一切都简单而优雅。但有一个细微之处:如果您向 Unmarshal 传递一个大的 JSON(例如,100 MB),那么您首先必须将这 100 MB 读入内存。然后 Unmarshal 会为结构体再分配内存。结果是 RAM 消耗翻倍。



json.NewDecoder.Decode — 水管工



Decoder 的工作方式不同:

  1. 创建一个新的解码器,它包装了 io.Reader。
  2. 解码器以缓冲区(默认 4 KB)为单位读取数据。
  3. 在读取过程中,它逐个令牌地解析 JSON 并填充结构体。


优点——您不必将整个 JSON 存储在内存中。缺点——管理缓冲区和流式读取的开销稍大。



实验:基准测试不会说谎



让我们在实践中验证理论。为三种场景编写一个简单的基准测试:

  • 小 JSON(1 KB)
  • 中等 JSON(100 KB)
  • 大 JSON(10 MB)


package main

import ( "bytes" "encoding/json" "testing")

type Data struct { ID int `json:"id"` Name string `json:"name"` Items []int `json:"items"`}

func generateJSON(size int) []byte { data := Data{ ID: 1, Name: "test", Items: make([]int, size), } for i := 0; i < size; i++ { data.Items[i] = i } b, _ := json.Marshal(data) return b}

func BenchmarkUnmarshal(b *testing.B) { jsonData := generateJSON(1000) // 1 KB var result Data for i := 0; i < b.N; i++ { json.Unmarshal(jsonData, &result) }}

func BenchmarkDecoder(b *testing.B) { jsonData := generateJSON(1000) // 1 KB var result Data for i := 0; i < b.N; i++ { reader := bytes.NewReader(jsonData) json.NewDecoder(reader).Decode(&result) }}


结果(在我的笔记本电脑上,Go 1.22):



大小

博客

书籍推荐