Qt字符串格式化性能比较

Qt字符串格式方法有三种, QString::arg(), QString::sprinft()和QStringList::join().
今天我做了个简单的性能测试, 希望对各位有所帮助.

调用QString::arg()一次:

  1. QString s("1234567890");

    // 开始计时
    for (int i = 0; i < 10000; ++i) {
        QString str("%1");
        str.arg(s);
    }
    // 停止计时

调用QString::arg()十次:

  1. QString s("1234567890");

    // 开始计时
    for (int i = 0; i < 10000; ++i) {
        QString str("%1%2%3%4%5%6%7%8%9%10");
        str
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s)
            .arg(s);
    }
    // 停止计时

调用QString::sprinft()一次:

  1. char s2[] = {"1234567890"};

    // 开始计时
        for (int i = 0; i < times; ++i) {
        QString().sprintf("%d", s2);
        }
        // 停止计时

调用QString::sprinft()十次:

  1. char s2[] = {"1234567890"};

    // 开始计时
        for (int i = 0; i < times; ++i) {
        QString().sprintf("%d%d%d%d%d%d%d%d%d%d", s2, s2, s2, s2, s2, s2, s2, s2, s2, s2);
        }
        // 停止计时

调用QStringList::join()一次:

  1. QString s("1234567890");

    // 开始计时
        for (int i = 0; i < times; ++i) {
        QStringList strList;
        strList.append(s);
        strList.join(QString());
        }
        // 停止计时

调用QStringList::join()十次:

  1. QString s("1234567890");

    // 开始计时
        for (int i = 0; i < times; ++i) {
        QStringList strList;
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.append(s);
        strList.join(QString());
        }
        // 停止计时

测试结果:
    运行一次的耗时: QString::arg() 0.412纳秒 < QStringList::join() 0.625纳秒 < QString::sprinft() 1.136纳秒;
    运行十次的耗时: QStringList::join() 2.426纳秒 < QString::arg() 5.175纳秒 < QString::sprinft() 9.232纳秒;

现在让我们来看看这些函数是怎么完成各自的任务的.

先来看看QString::arg(). 这个函数的实现非常简单,  它先将传入的参数转换成QString格式的字符串参数, 然后调用内部函数replaceArgEscapes来替换源字符串里的替换符(比如%1, %2, %3什么的). replaceArgEscapes首先计算一下新的字符串将会有多长, 然后预先创建一个足够长的临时字符串, 再将源字符串和字符串参数合并到临时字符串中. 一次操作就这么完成了.
    在这个过程中, 比较消耗时间的是创建了一个临时字符串. 随着累加调用QString::arg(), 当调用十次的时候就需要花费5.175纳秒, 其中至少有1.500纳秒是消耗在创建10个临时字符串当中. 还有重复多次的内存拷贝. 所以QString::arg()累加调用的越多, 它的执行效率越低.

下面再来看看QString::sprintf(), 这个函数是仿照C函数的sprintf(), 使用方法上一致, 这个函数也不复杂, 进去之后调用QString::vsprintf(), 在QString::vsprintf()中先创建个临时字符串, 然后类似于replaceArgEscapes的方式循环追加到这个临时字符串当中, 唯一的不同是, 这里没有计算最终字符串的大小, 调用的是QString::append(). 这个函数在缓存够大的时候直接追加数据, 如果不够大则重新分配足够大的内存. 所以QString::vsprintf()和QString::arg()本质上并没有太大区别. 因为每次都会生成个新的字符串, 并将数据拷贝进去.

好的东西都要放到最后面再说, 现在我们来看看QStringList::join()这个方法, 这个方法为什么合并10个数据只用了2.426纳秒的? 看了源代码就会豁然开朗了. 它先循环获取每个字符串的长度, 这样就可以计算出整个字符串的长度, 注意, 是整个, 不是一部分. 实际上不管QStringList里面有多少数据, 它合并的效率永远都是O(1).

那么说到这里, QString::arg()和QString::sprintf()的方法基本一样, 为什么差距那么大呢? 嘿嘿, 你们有没有发现. 我给arg传递的是已经构建完成的QString(), 而给QString::sprintf传递的是char *, 所以arg执行的时候不需要再把参数转换成字符串参数了, 这样省了10次构建QString()的代价, 而QString::sprintf不得不将char*类型的字符串转换成为QString(). 这也是没办法的事儿, 因为QString::sprintf()根本不接受QString()类型的参数.

现在我把QString::arg()和QString::join()里面预先构建的QString()字符串都替换成临时构建. 让我们来看看运行结果.

运行一次的耗时: QString::arg() 0.642纳秒 < QStringList::join() 0.792纳秒 < QString::sprinft() 1.146纳秒;
    运行十次的耗时: QStringList::join() 4.363纳秒 < QString::arg() 7.171纳秒 < QString::sprinft() 9.192纳秒;

这次算是公平的了, 从上面的数据可以看出来, 为什么官方不推荐使用QString::sprintf()这个函数了, 首先是这么调用不太符合Qt的代码风格, 除了兼容纯C/C++程序员的使用习惯. 基本上可以说一无是处了. 那么为啥也没有推荐使用QStringList.join()呢? 主要是这个函数的适应性不太好, 打个比方, "My father %1 has two bros, %2 was killed by malaria, %3 is still alive.", 如果用QString::arg()是如此简单, 如果用QStringList::join(), 那就要打断成这样: "My father " << "Old John" << " has two bros, " << "one" << " was killed by malaria, " << "another" << " is still alive", 一个是替换三个, 一个是合并7个.

我希望能通过这次测试各位从事Qt的朋友们, QString::sprintf()完全可以放弃了, 认为这个性能会如何如何好的, 现在应该知道了. 的确是后娘没人爱的一个函数; 剩下99%的时候都应该使用QString::arg() , 因为各位不会一下子格式化十个八个参数的, 一般都是一两个的, 这个的性能还是非常高的. 参数超过十个则要果断选择QStringList::join()了.

写了这么多, 希望能对各位有所帮助.

补充:
非常谢谢dbzhang800" data-card-url="pw_ajax.php?action=smallcard&type=showcard&uid=" target="_blank" onclick="return checkUrl(this)" id="url_1">dbzhang800的补充, QString::arg()的模板函数版本平时真的没怎么用过, 哈哈. 由于QString::arg()的模板版本不接受>=10的参数, 所以把十个参数改成九个参数进行比较.
下面是最新的性能比较结果. 
运行
    运行一次的耗时: QStringBuilder 0.362纳秒 < QString::arg() 0.636纳秒 < QString::arg() 模板版本 0.637纳秒 < QStringList::join() 0.821纳秒 < QString::sprinft() 1.275纳秒;
    运行十次的耗时: QStringBuilder 2.126纳秒 < QStringList::join() 3.781纳秒 < QString::arg() 模板版本 5.088纳秒 < QString::arg() 6.290纳秒 < QString::sprinft() 8.933纳秒;

从上面的运行结果来看, QString::arg()模板版本只是略微强于QString::arg()的多次调用版本. 函数内部仍然采用的是追加字符串的方式, 所以这种处理方式仍然面临多次分配内存的操作. 而且QString::arg()模板版本其实并不是模板函数, 只是一次接受多个QString类型的参数而已, 而且只能接受九个, 实际上它已经丢失了QString::arg()普通版本的一大优势--基本类型通杀. 也就是说, 如果你要使用QString::arg()的模板版本(姑且这么说吧), 那么你不得不将数据都自己转换成QString类型的, 否则你就没办法使用.
    牛叉的总要放在后面说, 是吧. dbzhang800" data-card-url="pw_ajax.php?action=smallcard&type=showcard&uid=" target="_blank" onclick="return checkUrl(this)" id="url_2">dbzhang800翻译的文章中已经对这个有详细的介绍了. 使用非常简单, #include <QStringBuilder>, 这个是内建的, 然后注意字符串连接时候的连接符是%, 而不是+.

再次感谢dbzhang800" data-card-url="pw_ajax.php?action=smallcard&type=showcard&uid=" target="_blank" onclick="return checkUrl(this)" id="url_4">dbzhang800的帮助, 希望大家能够继续补充!

dbzhang800 2011-10-17 17:23
呵呵,写的真不短啊。我补充一点。

都是QString的话,如果用 arg的话,应该选择:
QString("%1 %2 %3 %4").arg(s1, s2, s3, s4)
而不是
QString("%1 %2 %3 %4").arg(s1).arg(s2).arg(s3).arg(s4)

另外字符串链接的话:
s1 + s2 + s3 + s4
也是比较常用的,但性能就不如
s1 % s2 % s3 % s4 
这种写法了

Qt 格式化字符串的更多相关文章

  1. Qt sprintf_s函数格式化字符串出错

    Qt sprintf_s函数格式化字符串出错 问题的出现: 我在VS上用c C++写的跨平台的函数 移植到Qt 上面 出现sprintf_s 函数格式化出错. 开始以为是编码问题  反复查找Qt乱码问 ...

  2. VBA 格式化字符串 - Format大全

    VBA 格式化字符串 VBA 的 Format 函数与工作表函数 TEXT 用法基本相同,但功能更加强大,许多格式只能用于VBA 的 Format 函数,而不能用于工作表函数 TEXT ,以下是本人归 ...

  3. Python中用format函数格式化字符串

    Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者并存. 1.百分号方式 语法:%[( ...

  4. C#定义类型转化 及 格式化字符串

    operator 关键字 operator 关键字用来重载内置运算符,或提供类/结构声明中的用户定义转换.它可以定义不同类型之间采用何种转化方式和转化的结果. operator用于定义类型转化时可采用 ...

  5. 关于printf错用格式化字符串导致double和long double输出错误的小随笔

    [题外话] 以前用HUSTOJ给学校搭建Online Judge,所有的评测都是在Linux下进行的.后来为了好往学校服务器上部署,所以大家重新做了一套Online Judge,Web和Judge都是 ...

  6. .NET中DateTime.Now.ToString的格式化字符串

    .NET中DateTime.Now.ToString显示毫秒:DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") DateTime.N ...

  7. Python格式化字符串~转

    Python格式化字符串 在编写程序的过程中,经常需要进行格式化输出,每次用每次查.干脆就在这里整理一下,以便索引. 格式化操作符(%) "%"是Python风格的字符串格式化操作 ...

  8. %----format 格式化字符串---- 生成器---- 迭代器

    %方式格式化字符串 顺序传参数 o转换8进制x转换十六进制 tp1 = "i am %s" % "alex"tp2 = "i am %s age %d ...

  9. 飘逸的python - 增强的格式化字符串format函数

    自python2.6开始,新增了一种格式化字符串的函数str.format(),可谓威力十足.那么,他跟之前的%型格式化字符串相比,有什么优越的存在呢?让我们来揭开它羞答答的面纱. 语法 它通过{}和 ...

随机推荐

  1. html5的Canvas

    Canvas一般是指画布,最近对用html5写游戏比较感兴趣,所以简单的用了一下Canvas. 之前接触Canvas是在silverlight和wpf上用到过他,在silverlight上Canvas ...

  2. android bindService()

    bindService简介 Service一般用于不用显示,运行在后台的服务. startService 是我们最常用的启动Service的方法.而如何让service与其他组件通信呢?一般在一个进程 ...

  3. WebStorm修改默认快捷键

    打开 "File" -> "Setting" -> "keymap",即可修改.

  4. PHP XML Parser

    安装 XML Parser 函数是 PHP 核心的组成部分.无需安装即可使用这些函数. PHP XML Parser 函数 PHP:指示支持该函数的最早的 PHP 版本. 函数 描述 PHP utf8 ...

  5. SQL server概述

    sqlserver中包含的对象: 数据库.事务日志.索引.文件组.数据库关系图.视图.存储过程.用户自定义函数.用户.角色.程序集.表.报表.全文目录.用户自定义数据类型 数据库实际上是最高层对象,其 ...

  6. Dev-C++程序正确闪退问题

    只需要在主函数最后一句语句上面加一句getchar();即可

  7. PHP判断文章里是否有图片

    用preg_match来检查内容里是否有匹配的“<img”,其实我们还用preg_match来判断很多东西,比如邮箱地址里是否有“@”等,下面用一小段代码来演示具体用法. $content=&q ...

  8. php把数组保存成文件格式

    php把数组保存为文件格式的函数实例,或许有的还没听说过可以把数组保存成文件,其实这样做也是另有它用的,两种方法各有千秋,有兴趣的PHP爱好者敬请参阅: $file="./cache/fil ...

  9. MD5 加密 代码

    /*! 获取MD5 加密串 */ +(NSString *)getAuthorization:(NSString *)type Date:(NSDate *)date UserSecret:(NSSt ...

  10. 30种IT技能让你年薪过10万美元!

    科技行业的高薪岗位向来不少,但在不断变化的职场中,热门技能却随时在变:今天的热门技术,明天可能就会过时.美国求职网站 Dice.com 最近发布了 2015 年薪酬报告,通过对 23,470 位 IT ...