例如,我知道以下代码将在堆上分配两个字符串。
string s = "hello ";
s += "world!";
"hello"将保留在堆上,直到被垃圾回收;现在s引用堆上的"hello world!"。但是,以下代码会在堆上分配多少个字符串...是1个还是2个?另外,是否有一种工具/方法可以验证结果?
string s = "goodbye " + "cruel world!";
string s = "hello ";
s += "world!";
"hello"将保留在堆上,直到被垃圾回收;现在s引用堆上的"hello world!"。但是,以下代码会在堆上分配多少个字符串...是1个还是2个?另外,是否有一种工具/方法可以验证结果?
string s = "goodbye " + "cruel world!";
.method private hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 1
.locals init (
[0] string s)
L_0000: ldstr "goodbye cruel world!"
L_0005: stloc.0
L_0006: ldloc.0
L_0007: call void [mscorlib]System.Console::WriteLine(string)
L_000c: ret
}
正如您所看到的 (ldstr
),编译器已经为您完成了这个工作。
"hello "
并不驻留在堆上,而是位于程序的数据段 [参见注释] 中(因此不符合垃圾回收的条件),"world"
也是如此,对于 "hello world"
,如果编译器足够聪明,它也可能会被内部化。
"goodbye cruel world"
将被内部化,因为字符串字面量连接是由编译器处理的。
编辑:我不确定数据段声明,更多信息请参见this question。
实际上,可能是3个字符串。一个用于“goodbye”的常量字符串,一个用于“cruel world”的常量字符串,然后是一个新的字符串作为结果。
你可以通过查看生成的代码来确定。这取决于编译器(事实上,也取决于语言,这并不明显),但你可以使用-a标志(我想是这样,请查阅man页面)来读取g++的输出以获取中间代码。
在这里要小心,因为编译器在字符串值在编译时已知的情况下可以进行一些非常不同的优化。如果您使用的字符串直到运行时才知道(从配置文件、数据库或用户输入中提取),则会看到一些非常不同的IL。
如果不只是一行,那么可以通过将第一个字符串转换为StringBuffer,进行连接并返回结果字符串来完成两个字符串的连接。
自己创建StringBuffer可能看起来有些多余,但这实际上就是要发生的事情。
无论如何不要过早进行优化,但不要低估字符串连接的性能问题。这不是对象创建引起的,而是GC的工作。
Tess Ferrnandez's博客上有一个实验室,展示了字符串连接如何使服务器崩溃的(确实极端的)例子。
string s = "goodbye " + "cruel world!";
在Java中将会分配一个字符串。Java有些非常巧妙的技巧,很难被超越 - 只要不需要优化就不要优化!
然而,据我所知,目前使用如下代码:
String s="";
for(int i=0;i<1000;i++)
s+=" ";
创建一个1000个空格的字符串仍然往往非常低效。
在循环中添加字符串是相当糟糕的,但除此之外,它可能与StringBuilder一样高效。