Vor einiger Zeit habe ich eine c ++ / Qt-Anwendung geschrieben, die große Datenmengen im JSON- Format über das Netzwerk gesendet hat. Das Standard- QJsonDocument wurde verwendet . Während der Implementierung stieß ich auf eine geringe Leistung sowie auf ein unbequemes Klassendesign, das keine normale Erkennung von Fehlern während des Betriebs ermöglichte. Das Ergebnis war die JsonWriterSax- Bibliothek, mit der Sie JSON-Dokumente im SAX-Stil mit hoher Geschwindigkeit schreiben können, die ich unter der MIT-Lizenz auf github.com veröffentliche. Wen kümmert es - ich bitte um eine Katze.
Ein bisschen Theorie
JSON (JavaScript Object Notation) ist ein von Douglas Crockford entwickeltes strukturiertes Textdatenformat und eine Teilmenge der ECMAScript-Sprache (JavaScript, JScript usw. werden auf ihrer Grundlage erstellt). JSON ersetzt XML, erweitert die Verschachtelungsfunktionen und fügt Datentypen hinzu. Es wird derzeit im Internet aktiv genutzt.
Aber es gibt Mängel in JSON. Meiner Meinung nach gibt es unter den Standardtypen eindeutig nicht genug DateTime-Typ - Sie müssen den Wert in Form einer Zahl oder einer Zeichenfolge übertragen, und beim Parsen muss je nach Kontext eine Entscheidung getroffen werden. Es ist jedoch anzumerken, dass in ECMAScript der Datumstyp vor langer Zeit erstellt wurde, nicht durchdacht wurde und in der js-Welt Bibliotheken von Drittanbietern verwendet werden, um mit Datumsangaben zu arbeiten.
Es gibt zwei Hauptansätze zum Parsen und Erstellen strukturierter Dokumente - SAX und DOM. Sie wurden für XML angezeigt, können jedoch als Muster zum Erstellen von Handlern anderer Formate verwendet werden.
SAX (Simple API for XML)
Es wird für die sequentielle Datenverarbeitung verwendet und ermöglicht die Verarbeitung großer Dokumente in einem Stream. Beim Lesen werden die Anwendungsinformationen zu dem gefundenen Element oder Fehler zurückgegeben, die Beibehaltung der Informationen und die Verschachtelungssteuerung liegen jedoch bei der Anwendung selbst. Bei der Aufnahme werden normalerweise Schritte im Stil angezeigt: Starten Sie ein Element, starten Sie ein Unterelement, schreiben Sie eine Zahl, schreiben Sie eine Zeile, schließen Sie ein Unterelement, schließen Sie ein Element. Zu den Nachteilen gehört die Tatsache, dass der Programmierer den Code gründlicher schreiben muss, um die Struktur des Dokuments besser zu verstehen und das Fehlen oder die extreme Einschränkung der Bearbeitung eines vorhandenen Dokuments.
DOM (Document Object Model)
Mit dieser Methode wird ein Dokumentbaum im Speicher erstellt, der serialisiert, deserialisiert und geändert werden kann. Der Hauptnachteil ist der hohe Speicherverbrauch und die erhöhte Verarbeitungszeit. Unter der Haube wird normalerweise ein SAX-Handler verwendet.
QJsonDocument-Probleme
Das Standard-QJsonDocument verwendet den DOM-Ansatz. Beim Erstellen eines Dokuments ist die Geschwindigkeit niedrig - Sie können die Benchmarks am Ende des Artikels sehen. Das größte Problem für mich war jedoch das schlecht konzipierte Design der Fehlerrückgabe.
auto max = std::numeric_limits<int>::max(); QJsonArray ja; for(auto i = 0; i < max; ++i) { ja.append(i); if(ja.size() - 1 != i) { break; } }
In diesem Beispiel wird die Nachricht in den Fehlerstrom geschrieben, wenn nicht genügend Speicher vorhanden ist.
QJson: Document too large to store in data structure
und die Daten werden nicht mehr hinzugefügt. Im Fall eines Arrays können Sie die Bedingung überprüfen
ja.size() - 1 != i
Aber was tun bei der Arbeit mit einem Objekt? Ständig prüfen, ob ein neuer Schlüssel hinzugefügt wurde? Das Log auf der Suche nach einem Fehler analysieren?
Die Bibliothek
Die JsonWriterSax- Bibliothek ermöglicht das Schreiben eines JSON-Dokuments in einem QTextStream im SAX-Stil und ist auf github unter der MIT-Lizenz verfügbar. Die Speichersteuerung liegt bei der Anwendung. Die Bibliothek steuert die Integrität von JSON. Wenn das Element falsch hinzugefügt wird, gibt die Schreibfunktion einen Fehler zurück. Zur Steuerung wird eine KS-Grammatik verwendet. Tests wurden geschrieben, aber vielleicht wurde ein Fall unbeaufsichtigt gelassen. Wenn jemand den fehlerhaften Vorgang der Prüfung behebt und meldet, um den Fehler zu beheben, bin ich sehr dankbar.
Ich denke, dass die beste Beschreibung einer Bibliothek für einen Programmierer Codebeispiel ist =)
Beispiele
Array-Erstellung
QByteArray ba; QTextStream stream(&ba); stream.setCodec("utf-8"); JsonWriterSax writer(stream); writer.writeStartArray(); for(auto i = 0; i < 10; ++i) { writer.write(i); } writer.writeEndArray(); if(writer.end()) { stream.flush(); } else { qWarning() << "Error json"; }
Als Ergebnis bekommen wir
[0,1,2,3,4,5,6,7,8,9]
Objekterstellung
QByteArray ba; QTextStream stream(&ba); stream.setCodec("utf-8"); JsonWriterSax writer(stream); writer.writeStartObject(); for(auto i = 0; i < 5; ++i) { writer.write(QString::number(i), i); } for(auto i = 5; i < 10; ++i) { writer.write(QString::number(i), QString::number(i)); } writer.writeKey("arr"); writer.writeStartArray(); writer.writeEndArray(); writer.writeKey("o"); writer.writeStartObject(); writer.writeEndObject(); writer.writeKey("n"); writer.writeNull(); writer.write(QString::number(11), QVariant(11)); writer.write("dt", QVariant(QDateTime::fromMSecsSinceEpoch(10))); writer.writeEndObject(); if(writer.end()) { stream.flush(); } else { qWarning() << "Error json"; }
Als Ergebnis bekommen wir
{"0":0,"1":1,"2":2,"3":3,"4":4,"5":"5","6":"6","7":"7","8":"8","9":"9","arr":[],"o":{},"n":null,"11":11,"dt":"1970-01-01T03:00:00.010"}
Erstellen eines Dokuments mit Verschachtelung und verschiedenen Typen
QByteArray ba; QTextStream stream(&ba); stream.setCodec("utf-8"); JsonWriterSax writer(stream); writer.writeStartArray(); for(auto i = 0; i < 1000; ++i) { writer.writeStartObject(); writer.writeKey("key"); writer.writeStartObject(); for(auto j = 0; j < 1000; ++j) { writer.write(QString::number(j), j); } writer.writeEndObject(); writer.writeEndObject(); } writer.writeEndArray(); if(writer.end()) { stream.flush(); } else { qWarning() << "Error json"; }
Benchmarks
Wird von QBENCHMARK während des Release-Builds verwendet. Die Funktionalität ist in der JsonWriterSaxTest- Klasse implementiert.
elementares Betriebssystem 5.0 Juno, Kernel 4.15.0-38-generisch, CPU Intel® Core (TM) 2 Quad-CPU 9550 bei 2,83 GHz, 4 G RAM, Qt 5.11.2 GCC 5.3.1
Array mit langen Nummern
- QJsonDocument: 42 ms pro Iteration (insgesamt: 85, Iterationen: 2)
- JsonWriterSax: 23 ms pro Iteration (insgesamt: 93, Iterationen: 4)
Großes einstufiges Objekt
- QJsonDocument: 1.170 ms pro Iteration (insgesamt: 1.170, Iterationen: 1)
- JsonWriterSax: 53 ms pro Iteration (insgesamt: 53, Iterationen: 1)
Großes komplexes Dokument
- QJsonDocument: 1.369 ms pro Iteration (insgesamt: 1.369 ms, Iterationen: 1)
- JsonWriterSax: 463 ms pro Iteration (insgesamt: 463, Iterationen: 1)
elementares Betriebssystem 5.0 Juno, Kernel 4.15.0-38-generisch, CPU Intel® Core (TM) i7-7500U CPU bei 2,70 GHz, 8 G RAM, Qt 5.11.2 GCC 5.3.1
Array mit langen Nummern
- QJsonDocument: 29,5 ms pro Iteration (insgesamt: 118, Iterationen: 4)
- JsonWriterSax: 13 ms pro Iteration (insgesamt: 52, Iterationen: 4)
Großes einstufiges Objekt
- QJsonDocument: 485 ms pro Iteration (insgesamt: 485, Iterationen: 1)
- JsonWriterSax: 31 ms pro Iteration (insgesamt: 62, Iterationen: 2)
Großes komplexes Dokument
- QJsonDocument: 734 ms pro Iteration (insgesamt: 734, Iterationen: 1)
- JsonWriterSax: 271 ms pro Iteration (insgesamt: 271, Iterationen: 1)
MS Windows 7 SP1, CPU Intel® Core (TM) i7-4770 CPU bei 3,40 GHz, 8 G RAM, Qt 5.11.0 GCC 5.3.0
Array mit langen Nummern
- QJsonDocument: 669 ms pro Iteration (insgesamt: 669, Iterationen: 1)
- JsonWriterSax: 20 ms pro Iteration (insgesamt: 81, Iterationen: 4)
Großes einstufiges Objekt
- QJsonDocument: 1.568 ms pro Iteration (insgesamt: 1.568, Iterationen: 1)
- JsonWriterSax: 44 ms pro Iteration (insgesamt: 88, Iterationen: 2)
Großes komplexes Dokument
- QJsonDocument: 1.167 ms pro Iteration (insgesamt: 1.167, Iterationen: 1)
- JsonWriterSax: 375 ms pro Iteration (insgesamt: 375, Iterationen: 1)
MS Windows 7 SP1, CPU Intel® Core (TM) i3-3220 CPU bei 3,30 GHz, 8 G RAM, Qt 5.11.0 GCC 5.3.0
Array mit langen Nummern
- QJsonDocument: 772 ms pro Iteration (insgesamt: 772, Iterationen: 1)
- JsonWriterSax: 26 ms pro Iteration (insgesamt: 52, Iterationen: 2)
Großes einstufiges Objekt
- QJsonDocument: 2,029 ms pro Iteration (insgesamt: 2,029, Iterationen: 1)
- JsonWriterSax: 59 ms pro Iteration (insgesamt: 59, Iterationen: 1)
Großes komplexes Dokument
- QJsonDocument: 1.530 ms pro Iteration (insgesamt: 1.530, Iterationen: 1)
- JsonWriterSax: 495 ms pro Iteration (insgesamt: 495, Iterationen: 1)
Perspektiven
In zukünftigen Versionen möchte ich die Möglichkeit hinzufügen, das Format von Benutzerdaten mithilfe von Lambda-Funktionen mithilfe von QVariant zu beschreiben, die Möglichkeit hinzufügen, das Dokument (hübsches Dokument) durch Trennzeichen zu formatieren, und bei Interesse der Community einen SAX-Parser hinzufügen.
Um einen Überlauffehler zu finden, hat mir übrigens meine Bibliothek geholfen, mit der qInfo (), qDebug (), qWarning () das Format und die Ausgabe im Stil des Python- Protokollierungsmoduls festlegen können. Ich habe auch vor, diese Bibliothek in OpenSource zu veröffentlichen - wenn jemand interessiert ist - schreibe in die Kommentare.