使用加号进行单个字符串值的赋值会带来什么性能成本?

11

我经常在想,当我们给一个字符串变量赋值时,将它拆分成多行以提高可读性是否会有性能成本。我知道字符串是不可变的,因此每次都需要创建一个新字符串。但是,由于现在的硬件速度非常快(除非你处于某种恶魔般的循环中),实际上性能代价并不重要。举个例子:

String newString = "This is a really long long long long long" +
    " long long long long long long long long long long long long " +
    " long long long long long long long long long string for example.";

JVM或.Net的编译器和其他优化技术如何处理这个问题?它会创建一个字符串吗?还是先创建1个字符串,然后再新建一个连接值的字符串,接着再创建另一个连接值的字符串?

我只是出于好奇想知道这个问题的答案。

8个回答

29

这是由C#规范保证的,与在单个文本中创建字符串相同,因为它是编译时常量。来自C#3规范的第7.18节:

每当表达式满足上述要求时,该表达式将在编译时进行评估。即使表达式是包含非常量结构的较大表达式的子表达式,这也是正确的。

(请参见规范以获取“上述要求”的确切详细信息:)

Java语言规范在第3.10.5节的底部指定了它:

由常量表达式(§15.28)计算的字符串在编译时计算,然后被视为它们是文本。


谢谢Jon。我也是这么想的,但我的好奇心被激发了。再次感谢。 - uriDium

14

在Java中,编译器会将String转换为常量。

class LongLongString
{
    public LongLongString()
    {
        String newString = "This is a really long long long long long" +
            " long long long long long long long long long long long long " +
            " long long long long long long long long long string for example.";
    }

    public static void main(String[] args)
    {
        new LongLongString();
    }
}

编译后变成:

Compiled from "LongLongString.java"
class LongLongString extends java.lang.Object{
public LongLongString();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   ldc #2; //String This is a really long long long long long long long long long long long long long long long long long  long long long long long long long long long string for example.
   6:   astore_1
   7:   return

public static void main(java.lang.String[]);
  Code:
   0:   new #3; //class LongLongString
   3:   dup
   4:   invokespecial   #4; //Method "<init>":()V
   7:   pop
   8:   return

}

可以看到,在第4行中只加载了一行代码,而没有加载多个String实例。

编辑: 源文件是使用javac版本1.6.0_06编译的。查看Java语言规范(第三版)(以及Jon Skeet的答案提到的相同部分),我没有找到任何关于编译器是否应该将多行String连接成单个String的参考资料,因此这种行为可能是编译器实现特定的。


你可以看到编译器的特定版本做了什么,但是规范呢?我怎么知道呢? - Tom Hawtin - tackline

6

请自行测试。在C#代码中(Java等效也可以):

string x = "A" + "B" + "C";
string y = "ABC";

bool same = object.ReferenceEquals(x, y); // true

你会看到结果是true

另外,你会发现这个字符串也被存储在运行时的字符串池中:

bool interned = object.ReferenceEquals(x, string.Intern(x)); // true

你可以进行测试,但是规范呢?我怎么知道呢? - Tom Hawtin - tackline
第一部分(字符串引用相等性)由C#编译器控制,并且是指定的行为。然而,第二部分(内部化的字符串字面量)由CLR控制,并且在所有实现和平台上并不保证行为一致。 - Drew Noakes

5
没有性能损失。编译器的优化将把它合并成一个单一的字符串(至少在Java中)。

3

补充coobird的答案所需的等效.NET IL:

对于C#代码:

string s = "This is a really long long long long long" +
    " long long long long long long long long long long long long " +
    " long long long long long long long long long string for example.";
Console.WriteLine(s);

调试编译生成:

.method public hidebysig static void Main(string[] args) cil managed
{
  .custom instance void [mscorlib]System.STAThreadAttribute::.ctor()
  .maxstack 1
  .locals init (
      [0] string str)
  L_0000: ldstr "This is a really long long long long long long long long long long long long long long long long long  long long long long long long long long long string for example."
  L_0005: stloc.0 
  L_0006: ldloc.0 
  L_0007: call void [mscorlib]System.Console::WriteLine(string)
  L_000c: ret 
}

所以,正如你所看到的,这是一个字符串。


我能看到它,但我总是知道会发生什么吗?(好吧,如果我看别人的答案,我就知道了。) - Tom Hawtin - tackline
公正的观点。但在这种情况下,这是指定的行为,所以所有的C#编译器实现都应该按照这种方式工作。 - Drew Noakes

3
据我所记,这不会创建多个字符串,只会创建一个。

2
只要所有的字符串都是常量(就像在你的例子中一样),在Java(我想C#也是如此)中,编译器会将其转换为单个字符串。
只有当您连接大量动态字符串(例如在循环中)时才会出现+性能问题。 在这种情况下,请使用StringBuilder或StringBuffer。

0

免责声明:这适用于Java。我认为对于c#也是如此。

不仅javac会创建一个单一的字符串,而且JVM将使用一个字符串来表示所有包含相同文本的其他字符串。

String a = "He" + "llo th"+ "ere";
String b = "Hell" + "o the"+ "re";
String c = "Hello" +" "+"there";
assert a == b; // these are the same String object.
assert a == c; // these are the same String object.

注意:即使它们在不同的JAR包中,由不同的编译器编译,在运行时它们仍将是相同的String对象。

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