字符串不可变性

12
字符串不可变性是通过语句还是单个字符串实现的?
例如,我知道以下代码将在堆上分配两个字符串。
string s = "hello ";
s += "world!";

"hello"将保留在堆上,直到被垃圾回收;现在s引用堆上的"hello world!"。但是,以下代码会在堆上分配多少个字符串...是1个还是2个?另外,是否有一种工具/方法可以验证结果?

string s = "goodbye " + "cruel world!";
9个回答

21
编译器对字符串连接有特殊处理,这就是为什么第二个示例始终只有一个字符串的原因。而“interning”意味着即使您运行此行20000次,仍然只有1个字符串。
测试结果的最简单方法(在本例中)可能是查看反编译器。
.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),编译器已经为您完成了这个工作。


公平地说:在这种情况下,字符串的两个部分都在编译时已知。如果其中任何一部分需要等到运行时才能确定,那么你将会看到完全不同的IL代码。 - Joel Coehoorn
1
@Joel - 是的,但那就是问题所在。 - Marc Gravell

3
文字字符串是 内部化 的,这意味着 "hello " 并不驻留在堆上,而是位于程序的数据段 [参见注释] 中(因此不符合垃圾回收的条件),"world" 也是如此,对于 "hello world",如果编译器足够聪明,它也可能会被内部化。

"goodbye cruel world" 将被内部化,因为字符串字面量连接是由编译器处理的。


编辑:我不确定数据段声明,更多信息请参见this question


1
内部字符串实际上与.NET中的每个其他引用类型一样在堆上。 - Brian Rasmussen

0

实际上,可能是3个字符串。一个用于“goodbye”的常量字符串,一个用于“cruel world”的常量字符串,然后是一个新的字符串作为结果。

你可以通过查看生成的代码来确定。这取决于编译器(事实上,也取决于语言,这并不明显),但你可以使用-a标志(我想是这样,请查阅man页面)来读取g++的输出以获取中间代码。


0

在这里要小心,因为编译器在字符串值在编译时已知的情况下可以进行一些非常不同的优化。如果您使用的字符串直到运行时才知道(从配置文件、数据库或用户输入中提取),则会看到一些非常不同的IL。


0
如果你只是要做一或兩個字符串連接,那就不必擔心。
但是,如果你需要大量的連接,或者有一個循環操作,那麼你肯定需要採取預防措施。在Java世界中,這意味著你要使用StringBuffer而不是直接連接字符串。

在.NET中,它被称为StringBuilder。 - Mark Cidade
谢谢marxidad。我想到了类似的东西,因为它们在核心库中非常相似。 - Stephane Grenier

0

如果不只是一行,那么可以通过将第一个字符串转换为StringBuffer,进行连接并返回结果字符串来完成两个字符串的连接。

自己创建StringBuffer可能看起来有些多余,但这实际上就是要发生的事情。


我认为你的意思是 StringBuilder。 - Richard Szalay

0

0
不要轻信你对字符串的“了解”。你可能需要查看字符串实现的源代码。例如,你的例子:
string s = "goodbye " + "cruel world!";

在Java中将会分配一个字符串。Java有些非常巧妙的技巧,很难被超越 - 只要不需要优化就不要优化!

然而,据我所知,目前使用如下代码:

String s="";
for(int i=0;i<1000;i++)
    s+=" ";

创建一个1000个空格的字符串仍然往往非常低效。

在循环中添加字符串是相当糟糕的,但除此之外,它可能与StringBuilder一样高效。


那是一个相当大的“否则”…… StringBuilder将使用倍增,因此只需要<10次重新分配内存,而不是1000次复制(telescoping)。 - Marc Gravell
好的,所以现在,在大循环中避免向字符串追加,但是其他情况下不必过于担心。即使在这种情况下,对于大多数代码,我也不会担心,除非它开始影响性能。 - Bill K

-1
如果编译器“智能”,它将只返回一个字符串“再见残酷的世界!”

是的,它就是这样。请查看我回复中的IL。 - Marc Gravell
还可以在谷歌上搜索“实习池”。 - JamesSugrue

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接