天天看点

浅谈MFC CSting类拼接效率

暮鼓集    行走集

2004年11月10日

在使用MFC开发时,字符串类CString大概是使用最多的类之一,它使得对字符串的处理变得很方便。但是,在一些场合下,CString存在一些明显的效率问题。

如下面的代码:

CString a;

CString b = "Hello world!";

INT n = b.GetLength();
   
for( INT i = 0; i < n; i++ )
{
    a += b[i];
    a += TEXT("\r\n");
}    
           

这段代码将字符串b中的每个字符拼接到字符串a,并在后面插入0x0D 0x0A。

当b为”Hello, world!”这样简单的字符串时,程序的执行起来尚无问题。但是若b的字符串很长的时候,就会出现明显的效率问题。我使用自己的计算器做了一系列测试,得出如下的结果

字符串b的大小 执行时间

-------------─ 

  001k    0.01s

  010k    0.41s

  020k    1.68s

  040k    6.02s

  080k    33.56s

  100k    80.46s

可以说,当b的大小超过20K时,已经会出现明显的延迟,当超过40k,用户就会难以忍受了。

为甚么会出现这样的情况?我们会想到,当被拼接的字符串b越长,要复制和插入到字符串a的字就越多,循环的次数就越多,导致时间的增加。但是这只是一个方面,更为重要的原因在于--

CString的拼接操作符”+”看似简单,却要完成相当多的操作。首先会对CString对象(例子中字符串a)的数据缓冲区进行重新分配,接着找到字符串的结束位置,将要拼接的串复制到这个位置开始的缓冲区中。随着拼接的进行,CString对象的字符串随之变长,这些操作所需要的时间开销也会逐步增加。

如何解决这个问题呢,可以考虑,如果字符串a的数据缓冲区一开始就固定下来,那么此后在每次拼接前就不必去重新分配了;同时,用一个指针来指向已有字符串的结束位置,就不必去寻找这个位置了。因此,将程序改写如下。

CString a;
PTCHAR    buf, p;

CString b = "Hello, world!";
INT n = b.GetLength();
PTCHAR  q = b.GetBuffer(n);

p = buf = (PTCHAR)malloc( sizeof(TCHAR) * (n*3+1) );

while( *q )
{
    *p++ = *q++;
    *p++ = '\r';
    *p++ = '\n';
}

*p = '\0';
   
a = buf;
free( buf );
           

再次测试后的结果是

字符串b的大小 执行时间

-------------─ 

  040k    0.01s

  100k    0.01s

  500k    0.06s

效率的提升是多么明显。

题外话,这个例子的一个启示是,当遇到程序执行出现延迟的情形,可以首先从程序本身找原因,想办法替换掉那些效率不高的元操作。如果确认已经优化过了,还可以考虑其它的方法如多线程,来完成任务。