C++字符串拼接效率对比(+=、append、stringstream、sprintf)
作者:焱齿
C++字符串拼接效率对比
事情起因很简单,自己的代码中使用了stringstream对象进行字符串的拼接,然后被老同事质疑效率低下。借着这个机会了解下为什么?
一、+=、append、stringsteam、sprintf四种字符串拼接方法比较
C/C++中字符串拼接的使用场景非常多,字符串拼接的方法也非常多,这里简单的比对下上述四种方法的效率。
测试方法:
分别采用+=、append、stringstream、sprintf的方式来拼接字符串。
s1=“aaaaa”,s2=“bbbbb”,s3=“ccccc”。
内层循环将这三个字符串拼接100次;此外还有一个外层循环,循环次数自己定义(此处设为100000)。
程序如下:
#include <iostream> #include <string> #include <sys/time.h> #include <sstream> #include <stdio.h> using namespace std; #define OUT_IN_REPEATE_NUM 100000 #define IN_REPEATE_NUM 100 //内层循环将s1、s2、s3循环拼接100次 string s1="aaaaaa"; string s2="bbbbbb"; string s3="cccccc"; void plusTest(string& ret) { for(int i=0; i<IN_REPEATE_NUM; i++) { ret += s1; ret += s2; ret += s3; } } void appendTest(string& ret) { for(int i=0; i<IN_REPEATE_NUM; i++) { ret.append(s1); ret.append(s2); ret.append(s3); } } void sprintfTest(string& ret) { const size_t length=26*IN_REPEATE_NUM; char tmp[length]; char* cp = tmp; size_t strLength=s1.length()+s2.length()+s3.length(); for(int i=0; i<IN_REPEATE_NUM; i++) { sprintf(cp,"%s%s%s", s1.c_str(), s2.c_str(),s3.c_str()); cp+=strLength; } ret = tmp; } void ssTest(string& ret) { stringstream ss; for(int i=0; i<IN_REPEATE_NUM; i++) { ss<<s1; ss<<s2; ss<<s3; } ret = ss.str(); } int main() { string ss, plus, append, sprintf; struct timeval sTime, eTime; gettimeofday(&sTime, NULL); for(int i=0; i<OUT_IN_REPEATE_NUM; i++) { sprintf=""; sprintfTest(sprintf); } gettimeofday(&eTime, NULL); long SprintfTime = (eTime.tv_sec-sTime.tv_sec)*1000000+(eTime.tv_usec-sTime.tv_usec); //exeTime 单位是微秒 gettimeofday(&sTime, NULL); for(int i=0; i<OUT_IN_REPEATE_NUM; i++) { append=""; appendTest(append); } gettimeofday(&eTime, NULL); long AppendTime = (eTime.tv_sec-sTime.tv_sec)*1000000+(eTime.tv_usec-sTime.tv_usec); //exeTime 单位是微秒 gettimeofday(&sTime, NULL); for(int i=0; i<OUT_IN_REPEATE_NUM; i++) { ss=""; ssTest(ss); } gettimeofday(&eTime, NULL); long SsTime = (eTime.tv_sec-sTime.tv_sec)*1000000+(eTime.tv_usec-sTime.tv_usec); //exeTime 单位是微秒 gettimeofday(&sTime, NULL); for(int i=0; i<OUT_IN_REPEATE_NUM; i++) { plus=""; plusTest(plus); } gettimeofday(&eTime, NULL); long PlusTime = (eTime.tv_sec-sTime.tv_sec)*1000000+(eTime.tv_usec-sTime.tv_usec); //exeTime 单位是微秒 cout<<"PlusTime is : "<<PlusTime<<endl; cout<<"AppendTime is : "<<AppendTime<<endl; cout<<"SsTime is : "<<SsTime<<endl; cout<<"SprintfTime is :"<<SprintfTime<<endl; if(ss==sprintf && append==plus && ss==plus) { cout<<"result string are same!"<<endl; } else { cout<<"Different!"<<endl; cout<<"Sprintf: "<<sprintf<<endl; cout<<"ss: "<<ss<<endl; cout<<"Plus: "<<plus<<endl; cout<<"Append:"<<append<<endl; } }
结果如下:
可以看到+=、append、stringstream、sprintf四种方式在消耗的时间大致为1:1:4:2。
好吧,stringstream确实好慢,人家说的是对的。
二、关于stringstream
stringstream优点:可以方便的以流运算符<<将数值以各种数据(字串、数值)写入stringstream对象,且不用担心写越界等问题;其中类型安全不会溢出的特性非常抢眼。
stringstream缺点:相对于其他方法效率较低。一方面写入时的动态内存分配需要一定的开销,另一方面其成员函数str()在去除字符串的时候会进行一次字符串的值拷贝也影响效率。
stringstream对象的构造和析构函数通常是非常消耗时间,毕竟涉及到内存的分配、对象的构造。
上述测试结果也显示其效率明显低于”+=”、“append“。
当然这个时间消耗也是和stringstream对象被创建了多少次密切相关的。
也就是说如果能在多次转换(for循环)中重复使用同一个stringstream(而不是每次都创建一个新的对象)就还好。
但是记得每次循环使用前使用clear()、str("")方法(如下)。
void* test_stringstream(void * arg) { stringstream oss; for(int i=0;i<10000;i++) { oss.clear();这仅仅置流标记 oss.str("");/这是才是真正清空操作 oss << i; } }
字符串拼接执行速度和内存消耗比较
public static void main(String[] args) { long start = 0L; long end = 0L; System.out.println("字符串拼接执行效率比较:"); String s1 = ""; start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) {//十万次 s1 = s1 + "a"; } end = System.currentTimeMillis(); System.out.println("1、+ 方式拼接10万次耗时:" + (end - start) + "毫秒!"); String s2 = ""; start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) {//十万次 s2 += "b"; } end = System.currentTimeMillis(); System.out.println("2、+= 方式拼接10万次耗时:" + (end - start) + "毫秒!"); StringBuffer bf = new StringBuffer(); start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) {//千万次 bf.append("c"); } end = System.currentTimeMillis(); System.out.println("3、StringBuffer.append 方式拼接1000万次耗时:" + (end - start) + "毫秒!"); StringBuilder bl=new StringBuilder(); start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) {//千万次 bl.append("d"); } end = System.currentTimeMillis(); System.out.println("4、StringBuilder.append 方式拼接1000万次耗时:" + (end - start) + "毫秒!"); }
输出结果:
字符串拼接执行效率比较
1、+ 方式拼接10万次耗时:4561毫秒!
2、+= 方式拼接10万次耗时:4491毫秒!
3、StringBuffer.append 方式拼接1000万次耗时:189毫秒!
4、StringBuilder.append 方式拼接1000万次耗时:141毫秒!
解释:+ 方式本质是 s = new StringBuilder(s).append("a") .toString();
耗时间的地方不是 append,而是 toString,执行一次 toString 耗时在几微秒到几毫秒不等
内存消耗:
+ > += > StringBuffer = StringBuilder
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。