json.Unmarshal vs NewDecoder.Decode: Was ist schneller in Go?

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Zwei Brüder im Gleichgewicht: Unmarshal und Decode



Wenn Sie in Go programmieren und mit JSON arbeiten, stehen Sie früher oder später vor der Wahl: json.Unmarshal oder json.NewDecoder.Decode? Diese Frage ist so beliebt, dass sie auf Stack Overflow tausende Aufrufe und Dutzende Stimmen gesammelt hat. Aber die Antwort, die Sie dort finden, läuft oft auf "kommt auf die Aufgabe an" hinaus.



Heute geben wir nicht nur eine abstrakte Antwort. Wir führen unsere eigene Untersuchung durch: Wir analysieren die interne Funktionsweise beider Methoden, schreiben Benchmarks, zeigen reale Codebeispiele und leiten klare Regeln ab, wann was zu verwenden ist. Schnallen Sie sich an – es wird heiß!



Kurz und knapp: Was sind Unmarshal und Decode?



Beide Werkzeuge tun dasselbe – sie verwandeln JSON in eine Go-Struktur. Aber sie tun es auf unterschiedliche Weise.



  • json.Unmarshal – nimmt []byte und einen Zeiger auf die Struktur entgegen. Alles auf einmal: Bytes sind da, Struktur ist bereit, los geht's. Arbeitet "direkt" – deserialisiert das gesamte Datenarray im Speicher.
  • json.NewDecoder.Decode – arbeitet mit einem Stream (io.Reader). Sie erstellen einen Decoder, übergeben ihm einen Reader (z. B. os.File oder http.Response.Body) und rufen Decode auf. Der Decoder liest die Daten portionsweise, ohne alles auf einmal in den Speicher zu laden.


Der Unterschied scheint offensichtlich: Für große Daten – Decoder, für kleine – Unmarshal. Aber der Teufel steckt, wie immer, im Detail.



Die interne Funktionsweise: Wie sie unter der Haube arbeiten



json.Unmarshal – ein gezielter Schuss



Unmarshal ist eine Funktion, die:

  1. Ein Byte-Slice entgegennimmt.
  2. Speicher für die resultierende Struktur allokiert.
  3. In einem Durchlauf das gesamte JSON parst und die Struktur füllt.


Alles einfach und elegant. Aber es gibt einen Haken: Wenn Sie ein großes JSON (z. B. 100 MB) an Unmarshal übergeben, müssen Sie diese 100 MB zuerst in den Speicher lesen. Dann allokiert Unmarshal zusätzlichen Speicher für die Struktur. Ergebnis – doppelter RAM-Verbrauch.



json.NewDecoder.Decode – der Klempner



Der Decoder arbeitet anders:

  1. Ein neuer Decoder wird erstellt, der einen io.Reader umschließt.
  2. Der Decoder liest Daten in Puffern (standardmäßig 4 KB).
  3. Während des Lesens parst er das JSON Token für Token und füllt die Struktur.


Vorteil – Sie speichern nicht das gesamte JSON im Speicher. Nachteil – etwas mehr Overhead durch die Pufferverwaltung und das Streaming-Lesen.



Experiment: Benchmarks lügen nicht



Lassen Sie uns die Theorie in der Praxis überprüfen. Wir schreiben einen einfachen Benchmark für drei Szenarien:

  • Kleines JSON (1 KB)
  • Mittleres JSON (100 KB)
  • Großes 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) }}


Ergebnisse (auf meinem Laptop, Go 1.22):



Größe

Blogs

Buchempfehlungen