A = string.Concat("abc","def")
B = "abc" + "def"
A vs. B
最近我很困惑为什么很多人会说A与B相比肯定会做得更快。但是,他们只会说因为有人这么说或者因为它就是这样。我想我可以从这里听到更好的解释。
编译器如何处理这些字符串?
谢谢!
答案 0 :(得分:12)
当我加入C#编译器团队时,我做的第一件事就是重写了字符串连接的优化器。好时光。
如前所述,常量字符串的字符串连接在编译时完成。非常量字符串做了一些奇特的东西:
a + b --> String.Concat(a, b)
a + b + c --> String.Concat(a, b, c)
a + b + c + d --> String.Concat(a, b, c, d)
a + b + c + d + e --> String.Concat(new String[] { a, b, c, d, e })
这些优化的好处是String.Concat方法可以查看所有参数,确定它们的长度之和,然后创建一个可以容纳所有结果的大字符串。
这是一个有趣的。假设您有一个返回字符串的方法M:
s = M() + "";
如果M()返回null,则结果为空字符串。 (null + empty为空。)如果M不返回null,则空字符串的连接不会改变结果。因此,这实际上是优化的,因为根本不是对String.Concat的调用!它变成
s = M() ?? ""
整洁,嗯?
答案 1 :(得分:5)
在C#中,字符串的加法运算符只是String.Concat的语法糖。您可以通过在反射器中打开输出组件来验证。
另外需要注意的是,如果代码中有字符串文字(或常量),例如示例中,编译器甚至会将其更改为B = "abcdef"
。
但是,如果你使用String.Concat
和两个字符串文字或常量,仍然会调用String.Concat,跳过优化,因此+
操作实际上会更快。
所以,总结一下:
stringA + stringB
变为String.Concat(stringA, stringB)
"abc" + "def"
变为"abcdef
“
String.Concat("abc", "def")
保持不变
我必须尝试的其他东西:
在C ++ / CLI中,"abc" + "def" + "ghi
“实际上已转换为String.Concat(String.Concat("abc", "def"), "ghi")
答案 2 :(得分:5)
答案 3 :(得分:1)
实际上,B在编译期间得到解决。最终会得到B = "abcdef"
,而对于A,连接被推迟到执行时间。
答案 4 :(得分:1)
如果字符串是文字,就像你的问题一样,那么分配给B
的字符串的串联将在编译时完成。您的示例转换为:
string a = string.Concat("abc", "def");
string b = "abcdef";
如果字符串不是文字,那么编译器会将+
运算符转换为Concat
调用。
所以这......
string x = GetStringFromSomewhere();
string y = GetAnotherString();
string a = string.Concat(x, y);
string b = x + y;
...在编译时被翻译成这个:
string x = GetStringFromSomewhere();
string y = GetAnotherString();
string a = string.Concat(x, y);
string b = string.Concat(x, y);
答案 5 :(得分:1)
在这种特殊情况下,两者实际上是相同的。编译器会将第二个变量(使用+
运算符的变量)转换为对第一个变体Concat的调用。
嗯,就是说,如果两个实际包含连接的字符串变量。
此代码:
B = "abc" + "def";
实际上转换成了这个,没有连接:
B = "abcdef";
这可以完成,因为可以在编译时计算加法的结果,因此编译器会这样做。
但是,如果你要使用这样的东西:
A = String.Concat(stringVariable1, stringVariable2);
B = stringVariable1 + stringVariable2;
然后这两个将生成相同的代码。
但是,我想确切地知道那些“很多”所说的内容,因为我认为它是不同的。
我认为他们说的是字符串连接不好,你应该使用StringBuilder或类似的。
例如,如果你这样做:
String s = "test";
for (int index = 1; index <= 10000; index++)
s = s + "test";
然后会发生的是,对于循环中的每次迭代,您将构建一个新字符串,并让旧字符串有资格进行垃圾回收。
此外,每个这样的新字符串都会将旧字符串的所有内容复制到其中,这意味着您将移动大量内存。
以下代码:
StringBuilder sb = new StringBuilder("test");
for (int index = 1; index <= 10000; index++)
sb.Append("test");
将改为使用大于需要的内部缓冲区,以防您需要在其中附加更多文本。当该缓冲区变满时,将分配一个较大的缓冲区,并将旧的缓冲区留作垃圾收集。
因此,在内存使用和CPU使用方面,后一种变体要好得多。
除此之外,我会尽量避免过分关注“代码变体X比Y更好”,超出了你已经体验过的内容。例如,我现在使用StringBuilder只是因为我知道这种情况,但这并不是说我编写的所有使用它的代码实际上都需要它。
尽量避免花时间微优化代码,直到您知道自己有瓶颈为止。那个时候,关于测量的通常的提示,后来削减,仍然有效。