简介经常有客户打电话抱怨:你的程序慢得像蜗牛。您开始检查可能的可疑对象:文件IO、数据库访问速度,甚至查看Web服务。不过这些可能的疑惑都是正常的,完全没有问题。你用最方便的性能分析工具分析,发现瓶颈出在一个小函数上。该函数的作用是将一长串字符串链表写入文件。你对这个函数进行了如下优化:将所有的小字符串拼接成一个长字符串,执行一次文件写操作,避免数千次小字符串写文件操作。这个优化只对了一半。你先测试一个大字符串写入文件的速度,发现快如闪电。然后测试所有字符串连接的速度。几年。发生了什么?你将如何克服这个问题?您可能知道.net程序员可以使用StringBuilder来解决这个问题。这也是本文的出发点。背景如果你用谷歌搜索“C++StringBuilder”,你会得到很多答案。有些人会建议(你)使用std::accumulate,它几乎可以完成你想要实现的所有事情:#include//forstd::cout,std::endl#include//forstd::string#include//对于std::vector#include//对于std::accumulateintmain(){usingnamespacestd;vectorvec={"hello","","world"};strings=accumulate(vec.begin(),vec.end(),s);cout<classStringBuilder{typedefstd::basic_stringstring_t;typedefstd::listcontainer_t;//下面是不使用vector的原因。typedef类型名string_t::size_typesize_type;//在string.container_tm_Data;size_typem_totalSize;voidappend(conststring_t&src){m_Data.push_back(src);m_totalSize+=src.size();}//没有复制构造函数,没有赋值。StringBuilder(constStringBuilder&);StringBuilder&operator=(constStringBuilder&);public:StringBuilder(conststring_t&src){if(!src.empty()){m_Data.push_back(src);}m_totalSize=src.size();}StringBuilder(){m_totalSize=0;}//TODO:接受字符串数组的构造函数。StringBuilder&Append(conststring_t&src){append(src);返回*this;//允许链接。}//这允许您将任何STL容器添加到字符串生成器。templateStringBuilder&Add(constinputIterator&first,constinputIterator&afterLast){//std::for_each和lambda在这里看起来有点矫枉过正。//不使用std::copy,因为我们也想更新m_totalSize。for(inputIteratorf=first;f!=afterLast;++f){append(*f);}return*这;//允许链接。}StringBuilder&AppendLine(conststring_t&src){staticchrlineFeed[]{10,0};//C++11.感受爱!m_Data.push_back(src+lineFeed);m_totalSize+=1+src.size();return*this;//允许链接。}StringBuilder&AppendLine(){staticchrlineFeed[]{10,0};m_Data.push_back(lineFeed);++m_totalSize;return*this;//允许链接。}//TODO:AppendFormat实现。与文章无关。//类似于C#StringBuilder.ToString()//不是e使用reserve()来避免重新分配。string_tToString()const{string_tresult;//练习的重点!//如果容器有很多字符串,重新分配(每次结果增长)都会造成严重的损失,//在性能和机会方面失败。//我使用“保留”测量(在我无法发布的代码中)几分之一秒,使用+=.result.reserve(m_totalSize+1);//result=std::accumulate(m_Data)将近两分钟.begin(),m_Data.end(),结果);//这将失去'reserve'的优势for(autoiter=m_Data.begin();iter!=m_Data.end();++iter){result+=*iter;}returnresult;}//likejavascriptArray.join()string_tJoin(conststring_t&delim)const{if(delim.empty()){returnToString();}string_tresult;if(m_Data.empty()){returnresult;}//希望我们不要溢出大小type.size_typest=(delim.size()*(m_Data.size()-1))+m_totalSize+1;result.reserve(st);//如果你需要d喜欢C++11的理由,这是一个重新分配,如果“l”保留了足够的内存。string_toperator()(string_t&l,conststring_t&r){l+=m_Joiner;l+=r;returnl;}}adr(delim);autoiter=m_Data.begin();//跳过容器中第一个元素之前的分隔符。result+=*iter;返回std::accumulate(++iter,m_Data.end(),result,adr);}};//classStringBuilder有趣的部分是ToString()函数使用std::string::reserve()来最小化重新分配。您可以在下面看到性能测试的结果。函数join()使用std::accumulate()和一个已经为第一个操作数保留内存的自定义函数。你可能会问,为什么StringBuilder::m_Data使用std::list而不是std::vector?除非你有充分的理由使用另一个容器,否则std::vector通常是可行的方法。好吧,我(这样做)有两个原因:1.字符串总是附加到容器的末尾。std::list允许这样做而不需要重新分配内存;由于vector是使用连续的内存块实现的,因此每次使用都可能导致内存重新分配。2.std::list对顺序访问相当好,唯一对m_Data做的访问操作也是顺序的。您可以建议同时测试两种实现的性能和内存占用,然后选择一个。性能评估为了测试性能,我从维基百科上获取了一个网页,并将部分内容写入一个字符串向量中。然后,我编写了两个测试函数,第一个使用标准函数clock()并在两个循环中调用std::accumulate()和StringBuilder::ToString(),并打印结果。voidTestPerformance(constStringBuilder&tested,conststd::vector&tested2){constintloops=500;clock_tstart=clock();//放弃一些准确性以换取平台独立性。for(inti=0;iGuyRutenberg.timespecdiff(timespecstart,timespecend){timespectemp;if((end.tv_nsec-start.tv_nsec)<0){temp.tv_sec=end.tv_sec-start.tv_sec-1;temp.tv_nsec=1000000000+end.tv_nsec-start.tv_nsec;}else{temp.tv_sec=end.tv_sec-start.tv_sec;temp.tv_nsec=end.tv_nsec-start.tv_nsec;}returntemp;}voidAccurateTestPerformance(constStringBuilder&tested,conststd::vector&tested2){constintloops=500;timespectime1,time2;//不要忘记将-lrt添加到g++链接器命令行。//////////////////测试std::accumulate()///////////////clock_gettime(CLOCK_THREAD_CPUTIME_ID,&time1);for(inti=0;i。几年过去了,一些流的IO实现变得繁琐,现在的代码仍然没有完全摆脱它们的干扰。要使用这段代码,只需执行main函数所做的:创建StringBuilder的实例,用Append()、AppendLine()和Add()为其赋值,然后调用ToString函数检索结果。就像下面这样:intmain(){///////////////////////////////////8-位字符(ANSI)/////////////////////////////////StringBuilderansi;ansi.Append("Hello").Append("").AppendLine("World");std::cout<cargoCult{L"A",L"cargo",L"cult",L"is",L"a",L"kind",L"of",L"Melanesian",L"millenarian",L"movement",//这里还有很多行...L"applied",L"追溯",L"至",L"动向",L"在",L"一",L"多",L"早",L"时代。\n"};StringBuilder宽;wide.Add(cargoCult.begin(),cargoCult.end()).AppendLine();//使用ToString(),就像.netstd::wcout<