Qt格式化字符串
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);
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()了.
写了这么多, 希望能对各位有所帮助.
补充:
⾮常谢谢的补充, 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类型的, 否则你就没办法使⽤.
⽜叉的总要放在后⾯说, 是吧. 翻译的中已经对这个有详细的介绍了. 使⽤⾮常简单, #include <QStringBuilder>, 这个是内建的, 然后注意字符串连接时候的连接符是%, ⽽不是+.
再次感谢的帮助, 希望⼤家能够继续补充!
dbzhang8002011-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
字符串函数怎么获取这种写法了

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。