读取QJsonDocument如何避免额外拷贝?代码中哪些操作是拷贝?
关于QJsonDocument读取时减少拷贝的疑问与优化方案
问题背景
尝试读取QJsonDocument时希望尽量减少数据拷贝次数,看到他人示例代码仅读取操作就存在2-3次拷贝,但无法区分哪些操作是拷贝文档数据,哪些本质是指向文档的引用。自身对相关类的理解可能存在偏差:
- QJsonDocument类似顶层QJsonObject,提供文档级成员函数,存储整个文档。
- QJsonObject是有序树形对象,节点对应键,原以为它会拷贝所指向的文档片段,这显然低效。
- QJsonValueRef类似QJsonObject的引用,但不理解为何不用迭代器替代,且使用它似乎难以避免拷贝QJsonObject内容。
需要读取的JSON示例:
{ "FirstName": "John", "LastName": "Doe", "MiddleName": null, "Age": 43, "Address": { "Street": "Downing Street 10", "City": "London", "Country": "Great Britain" }, "Phone numbers": [ "+44 1234567", "+44 2345678" ], "Valid":true }
参考的解决方案代码(行编号用于提问):
QJsonObject jsonObj = jsonDoc.object(); // (1) QTextStream textStream(stdout); QJsonValue jsonVal = jsonObj.value(QString("Address")); // (2) QJsonObject jsonItemObj = jsonVal.toObject(); // (3) QJsonValue jsonSubVal; jsonSubVal = jsonItemObj["Street"]; // (4) textStream << jsonSubVal.toString() << endl; // (5) jsonSubVal = jsonItemObj["City"]; textStream << jsonSubVal.toString() << endl; jsonSubVal = jsonItemObj["Country"]; textStream << jsonSubVal.toString() << endl;
疑问:(1)-(5)中哪些操作属于拷贝?有没有办法消除这些拷贝?(注:(5)中仅指jsonSubVal.toString()而非textStream <<操作)
分析与优化方案
哪些操作是拷贝?
首先明确Qt的QJson系列类核心机制:QJsonDocument、QJsonObject、QJsonArray均基于隐式共享(Implicit Sharing,写时复制)的容器类,仅当修改指向的数据时才会触发实际拷贝,单纯读取操作大多只是复制轻量级容器对象(内部为指向数据的指针,开销极小),具体分析如下:
- 行(1):
QJsonObject jsonObj = jsonDoc.object();jsonDoc.object()返回QJsonObject,因隐式共享机制,此操作仅复制轻量级对象(内部指针指向原文档数据),未拷贝实际JSON数据,仅后续修改jsonObj时才会触发拷贝。 - 行(2):
QJsonValue jsonVal = jsonObj.value(QString("Address"));jsonObj.value()返回QJsonValue,这是一个轻量级封装类型,要么直接存储简单值,要么存储指向复杂类型(如QJsonObject/QJsonArray)的指针,未拷贝Address对象的实际数据,仅获取原数据的引用封装。 - 行(3):
QJsonObject jsonItemObj = jsonVal.toObject();
同样基于隐式共享,复制的QJsonObject仅为指向原Address数据的轻量级容器,未拷贝实际JSON键值对数据,修改时才会触发拷贝。 - 行(4):
jsonSubVal = jsonItemObj["Street"];jsonItemObj["Street"]返回QJsonValueRef(左值场景),赋值给QJsonValue时会转换为轻量级封装,未拷贝Street的字符串数据,仅引用原数据。 - 行(5):
jsonSubVal.toString()
此操作会拷贝字符串数据,因QString本身为隐式共享类型,toString()返回新的QString。若仅用于输出,该拷贝难以完全避免(Qt未提供直接获取原JSON字符串引用的接口)。
如何消除不必要的拷贝?
虽然上述大部分操作未实际拷贝JSON数据,但可通过引用或链式调用简化代码,减少多余中间对象:
优化后的简洁版本
// 用const引用接收顶层对象,避免轻量级容器复制(开销极小,但更严谨) const QJsonObject& jsonObj = jsonDoc.object(); QTextStream textStream(stdout); // 链式调用跳过中间变量 textStream << jsonObj["Address"].toObject()["Street"].toString() << endl; textStream << jsonObj["Address"].toObject()["City"].toString() << endl; textStream << jsonObj["Address"].toObject()["Country"].toString() << endl;
更安全的版本(避免键不存在导致的空值问题)
const QJsonObject& jsonObj = jsonDoc.object(); const QJsonValue addressVal = jsonObj.value("Address"); if (addressVal.isObject()) { const QJsonObject& addressObj = addressVal.toObject(); textStream << addressObj.value("Street").toString() << endl; textStream << addressObj.value("City").toString() << endl; textStream << addressObj.value("Country").toString() << endl; }
关键优化点:
- 使用**
const QJsonObject&**接收jsonDoc.object()和addressVal.toObject()的返回值,避免轻量级容器对象的复制。 - 链式调用减少不必要的中间变量创建。
- 若需多次复用字符串,可存储为
const QString&避免多次拷贝:const QString& street = addressObj.value("Street").toString(); textStream << street << endl; // 后续复用street时无需再次拷贝
关于QJsonValueRef的补充说明
QJsonValueRef并非用来替代迭代器,它是使用operator[]修改QJsonObject值时的代理类:例如jsonObj["Key"] = 123,jsonObj["Key"]返回的QJsonValueRef会直接修改原对象数据,避免拷贝整个QJsonObject。而迭代器用于遍历QJsonObject的键值对,两者用途不同。
内容的提问来源于stack exchange,提问作者Bobby
相关产品推荐
相关产品推荐

