しばらく前に、ネットワーク経由で大量のデータをJSON形式で送信するc ++ / Qtアプリケーションを作成しました。 標準のQJsonDocumentが使用されました 。 実装中に、パフォーマンスが低下するだけでなく、クラス設計が不便になり、操作中にエラーを正常に検出できませんでした。 その結果、 JsonWriterSaxライブラリが作成されました。これにより、JSONドキュメントをSAXスタイルで高速に記述できます。これは、MITライセンスの下でgithub.comに公開しています。 誰が気にする-私は猫をお願いします。
理論のビット
JSON(JavaScript Object Notation)は、Douglas Crockfordによって開発された構造化テキストデータ形式であり、ECMAScript言語のサブセット(JavaScript、JScriptなどがその基礎に基づいて作成されました)。 JSONはXMLを置き換え、ネスト機能を拡張し、データ型を追加します。 現在、インターネットで積極的に使用されています。
しかし、JSONには欠陥があります。 私の意見では、標準型の中には明らかに十分なDateTime型がありません-数値または文字列の形式で値を転送する必要があり、それを解析するときに、コンテキストに応じて決定を下します。 しかし、ECMAScriptでは、Date型はかなり前に作成され、考え抜かれておらず、jsの世界では、日付の処理にサードパーティのライブラリが使用されていることに注意してください。
構造化文書の解析と作成には、SAXとDOMの2つの主なアプローチがあります。 XMLにも登場しましたが、パターンとして使用したり、他の形式のハンドラーを作成したりできます。
SAX(XML用のシンプルなAPI)
順次データ処理に使用され、ストリーム内の大きなドキュメントを処理できます。 読み取り時には、見つかった要素またはエラーに関する情報をアプリケーションに返しますが、情報の保存とネスト制御はアプリケーション自体にあります。 記録の際、ステップは通常スタイルで示されます:要素の開始、サブ要素の開始、数値の書き込み、行の書き込み、サブ要素のクローズ、要素のクローズ。 欠点としては、プログラマーがコードをより徹底的に記述し、ドキュメントの構造と既存のドキュメントの編集の欠如または極端な制限をよりよく理解する必要があるという事実があります。
DOM(ドキュメントオブジェクトモデル)
このメソッドを使用すると、ドキュメントツリーがメモリ内に構築され、シリアル化、逆シリアル化、および変更が可能になります。 主な欠点は、高いメモリ消費と処理時間の増加です。 内部では、SAXハンドラーが通常使用されます。
QJsonDocumentの問題
標準のQJsonDocumentはDOMアプローチを使用します。 ドキュメントを作成するとき、速度は遅くなります-記事の最後にベンチマークを見ることができます。 しかし、私にとって最大の問題は、誤ったエラーリターンの設計でした。
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; } }
この例では、十分なメモリがない場合、メッセージはエラーストリームに書き込まれます。
QJson: Document too large to store in data structure
データは追加されなくなります。 配列の場合、条件を確認できます
ja.size() - 1 != i
しかし、オブジェクトを操作するときに何をすべきか 新しいキーが追加されたことを常に確認しますか? エラーを探してログを解析しますか?
図書館
JsonWriterSaxライブラリを使用すると、SAXスタイルのQTextStreamでJSONドキュメントを記述でき、MITライセンスの下でgithubで利用できます。 メモリ制御はアプリケーションにかかっています。 ライブラリはJSONの整合性を制御します-要素が誤って追加された場合、書き込み関数はエラーを返します。 制御には、KS文法が使用されます。 テストは作成されましたが、おそらくいくつかのケースは放置されました。 誰かがチェックの誤った操作を修正し、エラーを修正するために報告した場合-私は非常に感謝します。
プログラマーにとってのライブラリーの最良の記述はコード例だと思います=)
例
配列作成
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"; }
結果として、
[0,1,2,3,4,5,6,7,8,9]
オブジェクト作成
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"; }
結果として、
{"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"}
ネストと異なるタイプのドキュメントの作成
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"; }
ベンチマーク
リリースビルド中にQBENCHMARKによって使用されます。 機能はJsonWriterSaxTestクラスで実装されます。
基本OS 5.0 Juno、カーネル4.15.0-38-generic、CPUIntel®Core(TM)2 Quad CPU 9550 @ 2.83GHz、4G RAM、Qt 5.11.2 GCC 5.3.1
長い数字の配列
- QJsonDocument:反復あたり42ミリ秒(合計:85、反復:2)
- JsonWriterSax:反復あたり23ミリ秒(合計:93、反復:4)
大きな1レベルのオブジェクト
- QJsonDocument:反復あたり1,170ミリ秒(合計:1,170、反復:1)
- JsonWriterSax:反復あたり53ミリ秒(合計:53、反復:1)
大きな複雑なドキュメント
- QJsonDocument:反復あたり1,369ミリ秒(合計:1,369、反復:1)
- JsonWriterSax:反復あたり463ミリ秒(合計:463、反復:1)
基本OS 5.0 Juno、カーネル4.15.0-38-generic、CPUIntel®Core(TM)i7-7500U CPU @ 2.70GHz、8G RAM、Qt 5.11.2 GCC 5.3.1
長い数字の配列
- QJsonDocument:反復あたり29.5ミリ秒(合計:118、反復:4)
- JsonWriterSax:反復あたり13ミリ秒(合計:52、反復:4)
大きな1レベルのオブジェクト
- QJsonDocument:反復あたり485ミリ秒(合計:485、反復:1)
- JsonWriterSax:反復あたり31ミリ秒(合計:62、反復:2)
大きな複雑なドキュメント
- QJsonDocument:反復あたり734ミリ秒(合計:734、反復:1)
- JsonWriterSax:反復あたり271ミリ秒(合計:271、反復:1)
MS Windows 7 SP1、CPUIntel®Core(TM)i7-4770 CPU @ 3.40GHz、8G RAM、Qt 5.11.0 GCC 5.3.0
長い数字の配列
- QJsonDocument:反復あたり669ミリ秒(合計:669、反復:1)
- JsonWriterSax:反復あたり20ミリ秒(合計:81、反復:4)
大きな1レベルのオブジェクト
- QJsonDocument:反復あたり1,568ミリ秒(合計:1,568、反復:1)
- JsonWriterSax:反復あたり44ミリ秒(合計:88、反復:2)
大きな複雑なドキュメント
- QJsonDocument:反復あたり1,167ミリ秒(合計:1,167、反復:1)
- JsonWriterSax:反復あたり375ミリ秒(合計:375、反復:1)
MS Windows 7 SP1、CPUIntel®Core(TM)i3-3220 CPU @ 3.30GHz、8G RAM、Qt 5.11.0 GCC 5.3.0
長い数字の配列
- QJsonDocument:反復あたり772ミリ秒(合計:772、反復:1)
- JsonWriterSax:反復あたり26ミリ秒(合計:52、反復:2)
大きな1レベルのオブジェクト
- QJsonDocument:反復あたり2.029ミリ秒(合計:2.029、反復:1)
- JsonWriterSax:反復あたり59ミリ秒(合計:59、反復:1)
大きな複雑なドキュメント
- QJsonDocument:反復あたり1,530ミリ秒(合計:1,530、反復:1)
- JsonWriterSax:反復あたり495ミリ秒(合計:495、反復:1)
見込み
将来のバージョンでは、QVariantを使用してラムダ関数を通じてユーザーデータの形式を記述する機能を追加し、セパレーターを使用してドキュメント(きれいなドキュメント)をフォーマットする機能を追加し、コミュニティに関心がある場合は、SAXパーサーを追加できます。
ところで、オーバーフローエラーを見つけるために、私のライブラリが役に立ちました。これにより、qInfo()、qDebug()、qWarning()がPython ロギングモジュールのスタイルでフォーマットと出力を設定できます。 また、このライブラリをオープンソースに投稿する予定です-誰かが興味を持っているなら-コメントを書いてください。