Qt Json 库的效率优化

本文探讨了在使用QtJson库进行接口通信时可能遇到的效率问题,包括集合detach导致的不必要的拷贝、QJsonObject中字符串key的索引效率以及运行时的Unicode转换开销。提出了通过使用const引用、QLatin1String作为key以及优化查询方式等方法来提升性能。同时,强调了理解Qt集合的Copy-on-Write机制对于优化代码的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用 Qt Json 库,可以比较方便的实现基于 json 格式的接口通信。然而在通信消息比较频繁的情况下,容易成为效率瓶颈。本文就如何更有效率的使用 Qt Json 库,以及背后的一些相关实现细节做一些讨论。

使用 QtJson 一个例子

QByteArray data;
QJsonDocument doc = QJsonDocument::fromJson(data);
auto json = doc.object();
for (auto elem : json["items"].toArray()) {
    auto item = elem.toObject();
    auto name = item["name"].toString()
    bool enabled = item["status"].toString() == "enabled";
    qDebug() << "name" << name << "enabled" << enabled;
}

这段代码解析了下面这样的 json 数据:

{
    "items": [
        { "name": "X1", "status": "disabled" },
        { "name": "X2", "status": "enabled" },
        { "name": "X3", "status": "pressed" }
    ]
}

可是,就这样简简单单的 9 行代码,你知道里面有多少个效率问题吗?

问题1:集合 detach

首先,遍历 Qt 集合,容易引发不必要的集合拷贝。

我们知道 c++ for each 语法,实际上调用了集合的 begin()、end() 方法。如下,QJsonArray 中,每个方法实际上有 非 const 和 const 两个版本:

class Q_CORE_EXPORT QJsonArray
{
    ....
    inline iterator begin() { detach2(); return iterator(this, 0); }
    inline const_iterator begin() const { return const_iterator(this, 0); }
    ...
    inline iterator end() { detach2(); return iterator(this, size()); }
    inline const_iterator end() const { return const_iterator(this, size()); }
    ...
}

不注意的话,可能调用了集合的非 const 方法,然而非 const 方法,会调用 detach2(),从而引发集合拷贝。

你可能会奇怪,这个 detach 是什么东东?

其实 Qt 的很多集合都有 detach 机制。从头来说是这样的,Qt 集合使用了 Copy-on-White (COW)机制,也就是多个集合(拷贝的集合)共享内部状态,对于只读操作,共享没有任何问题;一旦某一个拷贝需要改变其状态,就需要拷贝出一份状态,在拷贝的状态上进行修改。

所以可以看到,Qt 集合类的非 const 方法,基本上先要执行 detach。

当然,并不是只要执行 detach 方法,就会拷贝内部状态,只有确实有多个集合共享内部状态时, 才需要拷贝内部状态。不幸的是,使用 QJsonDocument 中的 QJsonArray 肯定是一份拷贝,所以上面的代码必定要拷贝数组的内部状态了。

为解决该问题,可以改成这样:

auto const array = json["items"].toArray();
for (auto const & elem : array)

或者这样:

auto array = json["items"].toArray();
for (auto const & elem : qAsConst(array))

问题2:Object 索引查询效率

对于 QJsonObject 对象,使用字符串 key 去索引是很常见的操作,根据 key 的类型,它有两个版本:

class Q_CORE_EXPORT QJsonObject
{
    ...
    QJsonValue operator[] (QString const & key) const;
    QJsonValue operator[] (QLatin1String key) const { return value(key); }
    ...
}

当使用 QString 作为 Key 时,调用栈是这样的:

然后这个 ucstrncmp 的实现很复杂,用到了一些特殊指令集,有兴趣可以看看这里qstring.cpp source code [qtbase/src/corelib/text/qstring.cpp] - Woboq Code Browser 

当使用 QLatin1String 作为 Key 时,调用栈是这样的:

 最后的一个函数,是这样实现的,使用了 memcmp 函数。

inline bool operator<(QLatin1String s1, QLatin1String s2) noexcept
{
    const int len = qMin(s1.size(), s2.size());
    const int r = len ? memcmp(s1.latin1(), s2.latin1(), len) : 0;
    return r < 0 || (r == 0 && s1.size() < s2.size());
}

显然,第一种方式,需要比较 Unicode 和 latin 字符集两种字符串,性能劣于简单的 latin 字符串比较。另外,第一种方式,还需要将 key 从 "items" 转换为 QString,QString 需要分配内存来存储 Unicode 字符串,还要进行转换字符集转换,对内存性能、CPU性能都会有影响。

那我们的 json["items"] 到底是调用哪个版本呢?很遗憾,是 QString 作为 key 的版本。为了使用第二种方式的字符串比较,需要将代码改为:

static QLatin1String KEY_ITEMS("items");
auto array = json[KEY_ITEMS].toArray();

问题3:运行时 Unicode 转换

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting Horse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值