JSON-Streaming für große Dateien: Verarbeiten ohne alles zu laden
Standard-JSON-Parsing lädt das gesamte Dokument in den Speicher, baut eine vollständige Datenstruktur auf und gibt Ihnen dann Zugriff. Für eine 10-MB-Datei funktioniert das problemlos. Für eine 10-GB-Datei läuft Ihr Prozess aus dem Speicher und stürzt ab. Streaming-Parser lösen dieses Problem, indem sie JSON inkrementell verarbeiten — Daten lesen und verarbeiten, sobald sie ankommen, ohne jemals das gesamte Dokument im Speicher zu halten.
Das Problem mit Standard-Parsing
import json
# This loads the ENTIRE file into memory
with open('huge.json') as f:
data = json.load(f) # 10 GB file = 10+ GB of RAM
# Processing happens after full load
for item in data['records']:
process(item)
Für eine Datei mit 1 Million Datensätzen zu je 10 KB benötigt Standard-Parsing:
- Dateigröße: ~10 GB
- Speicher für Parsing: ~10 GB (der rohe String)
- Speicher für Datenstruktur: ~15-20 GB (Python-Objekte sind größer als rohes JSON)
- Gesamt: ~25-30 GB RAM
Streaming reduziert dies auf Megabytes.
Streaming-Ansätze
SAX-Style (Ereignisbasiert)
Der Parser gibt Ereignisse aus, wenn er JSON-Token erkennt:
import ijson
# Process items one at a time - constant memory usage
with open('huge.json', 'rb') as f:
for record in ijson.items(f, 'records.item'):
process(record) # Each record is parsed individually
# Previous records are garbage collected
Ereignisse umfassen: start_map, map_key, end_map, start_array, end_array, string, number, boolean, null.
JSON Lines (JSONL / NDJSON)
Ein einfacherer Ansatz: ein JSON-Objekt pro Zeile. Jede Zeile ist ein vollständiges, gültiges JSON-Dokument:
{"id": 1, "name": "Alice", "email": "alice@example.com"}
{"id": 2, "name": "Bob", "email": "bob@example.com"}
{"id": 3, "name": "Charlie", "email": "charlie@example.com"}
Die Verarbeitung ist trivial — Zeile für Zeile lesen:
with open('data.jsonl') as f:
for line in f:
record = json.loads(line)
process(record)
Vorteile von JSON Lines:
- Jede Zeile ist unabhängig parsebar (parallele Verarbeitung)
- Anhängefreundlich (einfach eine neue Zeile hinzufügen)
- Funktioniert mit Standard-Unix-Tools (
grep,wc,head,tail) - Natürlich für Logdateien und Streaming-Daten
Chunk-basierte Verarbeitung
Für Standard-JSON-Arrays die Verarbeitung in Chunks aufteilen:
import ijson
def process_in_chunks(filename, chunk_size=1000):
chunk = []
with open(filename, 'rb') as f:
for record in ijson.items(f, 'item'):
chunk.append(record)
if len(chunk) >= chunk_size:
process_batch(chunk)
chunk = []
if chunk:
process_batch(chunk)
Sprachspezifische Implementierungen
Python (ijson)
import ijson
# Stream from file
with open('large.json', 'rb') as f:
parser = ijson.parse(f)
for prefix, event, value in parser:
if prefix == 'records.item.name':
print(value)
# Stream from HTTP response
import urllib.request
response = urllib.request.urlopen('https://api.example.com/data')
for record in ijson.items(response, 'records.item'):
process(record)
JavaScript (Node.js)
const { createReadStream } = require('fs');
const { parser } = require('stream-json');
const { streamArray } = require('stream-json/streamers/StreamArray');
const pipeline = createReadStream('large.json')
.pipe(parser())
.pipe(streamArray());
pipeline.on('data', ({ value }) => {
process(value);
});
pipeline.on('end', () => {
console.log('Done processing');
});
Kommandozeile (jq)
# Stream mode - process objects individually
jq --stream 'select(length == 2) | .[1]' large.json
# Process JSON Lines
cat data.jsonl | jq -c 'select(.age > 30)'
# Convert array to JSON Lines
jq -c '.[]' large_array.json > data.jsonl
Wann Streaming verwenden
| Szenario | Standard | Streaming |
|---|---|---|
| Datei unter 100 MB | Bevorzugt | Übertrieben |
| Datei 100 MB bis 1 GB | Hängt vom RAM ab | Empfohlen |
| Datei über 1 GB | Nicht machbar | Erforderlich |
| HTTP-Antwort (groß) | Timeout-Risiko | Als Stream empfangen |
| Echtzeit-Datenfeed | Nicht anwendbar | Erforderlich |
| Einfaches einmaliges Lesen | Bevorzugt | Unnötig |
Leistungsvergleich
Verarbeitung von 1 Million Datensätzen (1 GB Datei):
| Ansatz | Speicherverbrauch | Verarbeitungszeit | Komplexität |
|---|---|---|---|
| json.load() | 3-5 GB | 15 Sek. | Einfach |
| ijson Streaming | 50 MB | 45 Sek. | Mittel |
| JSON Lines | 10 MB | 12 Sek. | Einfach |
| Chunk-basiert (1000) | 100 MB | 20 Sek. | Mittel |
Streaming verbraucht weit weniger Speicher, ist aber langsamer beim SAX-Style-Parsing aufgrund des ereignisgesteuerten Overheads. JSON Lines ist sowohl schnell als auch speichereffizient, da jede Zeile eine unabhängige Parse-Operation ist.
Best Practices
- JSON Lines verwenden, wenn möglich: Es ist das einfachste Streaming-Format und funktioniert mit Standard-Tools.
- Puffergrößen festlegen: Lesepuffer für optimalen Durchsatz konfigurieren (typischerweise 64 KB bis 1 MB).
- In Batches verarbeiten: Datenbank-Inserts und API-Aufrufe bündeln, anstatt einen Datensatz nach dem anderen zu verarbeiten.
- Fehler elegant behandeln: Beim Streaming sollte ein fehlerhafter Datensatz nicht die gesamte Pipeline zum Absturz bringen.
- Speicher überwachen: Profiling verwenden, um zu überprüfen, dass Streaming den Speicher tatsächlich begrenzt hält.
Für die Formatierung und Validierung kleinerer JSON-Dateien verarbeitet unser JSON Formatter Dokumente bis 100 MB mit Echtzeit-Formatierung.
FAQ
Kann ich JSONPath mit Streaming-Parsern verwenden?
Einige Streaming-Bibliotheken unterstützen pfadbasierte Filterung. Python ijson.items unterstützt das Filtern nach Pfad während des Streamings. Komplexe JSONPath-Abfragen (Filter, Wildcards über Ebenen hinweg) benötigen jedoch typischerweise das vollständige Dokument im Speicher. Für pfadbasierte Abfragen siehe unseren JSONPath-Leitfaden.
Wie konvertiere ich ein großes JSON-Array zu JSON Lines?
Verwenden Sie jq -c '.[]' input.json > output.jsonl für Dateien, die in den Speicher passen. Für wirklich große Dateien verwenden Sie einen Streaming-Konverter: Lesen Sie das Array mit einem Streaming-Parser und schreiben Sie jedes Element als Zeile.
Verwandte Ressourcen
- JSON Formatter — JSON-Dateien formatieren und validieren
- JSONPath-Abfrage-Leitfaden — Daten effizient aus JSON extrahieren
- JSON-Editor-Tipps — Mit großen JSON-Dokumenten arbeiten