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