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 个字符串,然后创建一个新的连接值,然后再创建另一个连接值?

这是出于我自己的好奇心。

4

8 回答 8

28

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

只要表达式满足上面列出的要求,就会在编译时评估该表达式。即使表达式是包含非常量结构的较大表达式的子表达式,也是如此。

(有关“上述要求”的详细信息,请参阅规范:)

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

由常量表达式(第 15.28 节)计算的字符串在编译时计算,然后将其视为文字。

于 2009-03-02T09:59:04.230 回答
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 编译的。查看The Java Language Specification, Third Edition(以及Jon Skeet 的回答中提到的同一部分),我无法找到任何关于编译器是否应该将多行连接String成单行的参考String,所以这种行为可能是编译器特定于实现。

于 2009-03-02T10:03:54.313 回答
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
于 2009-03-02T10:05:31.717 回答
5

没有性能权衡。编译器的优化会将其合并为单个字符串(至少在 Java 中)。

于 2009-03-02T09:59:39.283 回答
3

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

于 2009-03-02T09:58:12.377 回答
3

等效的 .NET IL 来补充coobird 的答案

对于 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 
}

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

于 2009-03-02T11:04:46.273 回答
2

只要所有字符串都是常量(就像在您的示例中一样),在 Java(我想是 C#)中,编译器会将其转换为单个字符串。

如果您连接大量动态字符串(例如在循环中),您只会遇到 + 的性能问题。在这种情况下,使用 StringBuilder 或 StringBuffer。

于 2009-03-02T09:59:51.200 回答
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.

注意:它们在运行时将是相同的 String 对象,即使它们位于不同 JARS 的不同类中,由不同的编译器编译。

于 2009-03-02T21:59:46.680 回答