前一段时间,我编写了一个c ++ / Qt应用程序,该应用程序通过网络发送了JSON格式的大量数据。 使用标准QJsonDocument 。 在实施过程中,我遇到了性能低下以及类设计不便的问题,无法正常检测操作过程中的错误。 结果就是JsonWriterSax库,该库允许您以SAX样式高速编写JSON文档,该库是我在MIT许可下发布在github.com上的。 谁在乎-我要猫。
一点理论
JSON(JavaScript对象表示法)是Douglas Crockford开发的结构化文本数据格式,并且是ECMAScript语言的子集(JavaScript,JScript等是在其基础上创建的)。 JSON取代了XML,扩展了嵌套功能并添加了数据类型。 当前,它在Internet上得到了积极使用。
但是JSON中存在缺陷。 我认为,在标准类型中,显然没有足够的DateTime类型-您必须以数字或字符串的形式传输值,并且在解析它时,请根据上下文进行决策。 但是,值得注意的是,在ECMAScript中,Date类型是很久以前创建的,并未被考虑,在js世界中,第三方库用于处理日期。
解析和创建结构化文档有两种主要方法-SAX和DOM。 它们是为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的完整性-如果未正确添加元素,则write函数将返回错误。 为了进行控制,使用了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,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)
大一级对象
- 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,cpu英特尔®酷睿(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)
大一级对象
- 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)
大一级对象
- QJsonDocument:每次迭代1,568毫秒(总计:1,568,迭代:1)
- JsonWriterSax:每次迭代44毫秒(总计:88,迭代:2)
大复杂文件
- QJsonDocument:每次迭代1,167毫秒(总计:1,167,迭代:1)
- JsonWriterSax:每次迭代375毫秒(总计:375,迭代:1)
MS Windows 7 SP1,CPU英特尔®酷睿(TM)i3-3220 CPU @ 3.30GHz,8G RAM,Qt 5.11.0 GCC 5.3.0
长数数组
- QJsonDocument:每次迭代772毫秒(总计:772,迭代:1)
- JsonWriterSax:每次迭代26毫秒(总计:52,迭代:2)
大一级对象
- QJsonDocument:每次迭代2.029毫秒(总计:2.029,迭代:1)
- JsonWriterSax:每次迭代59毫秒(总计:59,迭代:1)
大复杂文件
- QJsonDocument:每次迭代1,530毫秒(总计:1,530,迭代:1)
- JsonWriterSax:每次迭代495毫秒(总计:495,迭代:1)
前景展望
在未来的版本中,我计划添加使用QVariant通过lambda函数描述用户数据格式的功能,添加使用分隔符来格式化文档(漂亮文档)的功能,并且,如果社区感兴趣,我可以添加SAX解析器。
顺便说一句,为了找到溢出错误,我的库为我提供了帮助,它允许qInfo(),qDebug(),qWarning()设置Python 日志记录模块样式的格式和输出。 我还计划将此库以开放源代码发布-如果有人感兴趣,请在评论中写下。