StringBuilder等字符连接的性能分析(Concatenating Strings Efficiently)

     许多开发者认为改善性能的途经之一是使用StringBuilder去连接strings,其实这有一定的误解!但幸运的是它有时候确实能改善性能!如下:
     1 .我们应该避免以下问题
using System;
public class Test
{
static void Main()
{
DateTime start = DateTime.Now;
string x = "";
for (int i=0; i < 100000; i++)
{
x += "!";
}
DateTime end = DateTime.Now;
Console.WriteLine ("Time taken: {0}", end-start);
}
}
      在我比较快的笔记本电脑中,这段代码花费了我10秒钟。如果再翻倍 ,则它超过了1分钟(注:结果在.NET2.0 beta中轻微的要好些,但是这种改变不是很大)。这里真正的问题在于strings是不可变的,我们使用了+=而不是真正的意味着让runtime(运行时)去添加存在字符串到已经存在字符串的尾部。实际上,x  += "!" 就等于 x = x + "!".这种连接相当于重新创建了一个全新的string(并且为此分配了相相当大的内存),首先从原来已经存在的值的x中Copy所有的值,然后再把添加了!的值Copy过来。当这个string快速增长的时候,将数据copy的数量也在增加,这就是为什么我添加2倍循环的时候则时间花费不是原来的2倍,而是超过了!上面的代码明显的是低效率的,这时候stringbuilder就派上用场了!
2. StringBuilder解决方案
下面是一段与上面代码等价的程序,我们改用了stringBuilder,你会感觉到速度快了好多!
using System;
using System.Text;
public class Test
{
static void Main()
{
DateTime start = DateTime.Now;
StringBuilder builder = new StringBuilder();
for (int i=0; i < 100000; i++)
{
builder.Append("!");
}
string x = builder.ToString();
DateTime end = DateTime.Now;
Console.WriteLine ("Time taken: {0}", end-start);
}
}

同样在我的笔记本电脑中,速度快了好多,我将循环提高了10倍(在第一程序中花了我10秒钟),而在这段程序中才花了30-40MS,而且粗略的看,这是时间的提升是线性的,也就是说我提高2倍的循环,它就花费2次的时间.它避免了不必要的copy(唯一的copy就是append中的字符串copy).StringBuilder维持了一个内部的缓冲区去添加这些string(实际上,内部的缓冲区也是一个string,这个string是不可变的,来源于一个公共的接口,而不是来源于mscorlib的内部)。我们能在改变上面的的代码使其更加具有效率就是通过给stringBuilder设定一个正确的缓冲区大小。这样它就会避免一些不必要的copy。
      3.我可以在任何地方都 使用stringBuilder???
       又回到我们上边的刚开始的话题了,这当然不是绝对的,看看下面的例子:
string name = firstName + " " + lastName;
Person person = new Person (name);
     有些人写成这样:
 
// Bad code! Do not use! 
StringBuilder builder = new StringBuilder();
 builder.Append (firstName); 
builder.Append (" ");
 builder.Append (lastName); 
string name = builder.ToString(); 
Person person = new Person (name);
 现在,在更广泛的观点上,有人认为第二个版本比第一个版本效率要高一些,但它有可能不是非常有效率的,除非那段代码调用了非常非常多的数量,则它花在连接上的时间可能会小。而且为了第二个版本的代码那么少的效率而缺少 了第一段代码的可读性是一个bad idea。
但是,实际上第二段代码是低效率的比第一段代码。第一个版本的compiles时 调用了string.concat,像这样:
string name = String.Concat (firstName, " ", lastName);
Person person = new Person (name);
string.concat能够将许多的strings和对象连接到一起,它是简单而有效的。并且strign.concat有很多的重载
string.concat没有多余的copy.数据只copy一次到一个新的string,这个string刚刚好有一个正确的长度。
比较stringbuilder版本,它不知道要连接的字符串的缓冲区大小(因为我们没有告诉它),这就意味着
它必须copy比它实际需要还要大的缓冲区。更重要的是前一个方法我们只要调用一次string.concat就
可以将字符串连接在一起,而后面则多了几次中间结果的copy.
4.常量
当是几个常量进行的连接的时候,事情变得也不一样了,你是否会觉得string x= "hello" + "there"
编绎的时候也会调用string.concat,其实你错了,它实际上被编译成string x="hello there".编绎器
知道所有的部分都是常量,因为它在编绎的进候做连接,将全部的string存储在编绎的代码中。如果这时候
将其转换成stringbuilder,那么不管从内存上都是时间上来讲都是低效率的,而且缺少了代码可读性
5.规则
那么我们什么是用stringbuilder,什么时候用连接操作符呢?
1. 明确使用stringbuilder:当你在一个巨大的循坏中进行连接操作,并用你不确实你的循环次数的时候。
例如:每一次读一个文件的一个字符,如果这时候你用+=等于就是自杀!
2. 明确使用操作连接符:当你在一句话中能明确指定连接的时候(如果你有数组去连接,考虑调用string.concat或如果需要分隔符则调用string.join)
3. 不要担心将一个比较长的文字拆成多个部分,因为这样增加了易读性,并且不损坏性能
4. 如果你一些字符串要连接,并且想分成多个语句实现。这个方式的效率取决于你要连接的字符串的
数量,已经连接的先后。如果你认为这段代码是你程序中的一个性能瓶经,则应该在各种方法中找到一个平
衡点!
英文地址(引用):Concatenating Strings Efficiently
posted @ 2008-10-10 01:14  译时代  阅读(471)  评论(4编辑  收藏  举报