连接多个字符串时会创建多少个字符串对象?
How many String objects would be created when concatenating multiple Strings?
我在一次采访中被问及将在给定问题上创建的对象数量:
String str1 = "First";
String str2 = "Second";
String str3 = "Third";
String str4 = str1 + str2 + str3;
我回答说在字符串池中会创建 6 个对象。
3 would be for each of the three variables.
1 would be for str1 + str2
(let's say str
).
1 would be for str2 + str3
.
1 would be for the str + str3
(str = str1 + str2
).
我给的答案正确吗?如果不是,正确答案是什么?
应该是5:
三个用于三个文字(分配给 str1
、str2
和 str3
)
一个 str1 + str2
一个给(result from the previous operation) + str3
(分配给str4
)
Java 8 可能会创建 5 个对象:
- 3 代表 3 个文字
- 1
StringBuilder
- 1 表示串联
String
尽管 Java 9 things changed 和 String
连接不再使用 StringBuilder
。
根据给定的信息,无法明确回答问题。正如 JLS, §15.18.1 中所述:
... To increase the performance of repeated string concatenation, a Java compiler may use the StringBuffer
class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.
这意味着答案至少取决于所使用的具体 Java 编译器。
我认为我们能做的最好的就是给出一个区间作为答案:
- 一个聪明的编译器可能能够推断出
str1
到 str3
从未被使用过,并在编译期间折叠连接,这样只创建一个 String
-object(一个被 str4
) 引用
- 创建的
String
的最大合理数量应为 5:str1
到 str3
各一个,tmp = str1 + str2
一个,[=19= 一个].
所以...我的答案是 "something between one to five String
-objects"。至于只为此操作创建的对象总数……我不知道。这也可能取决于例如StringBuffer
已实施。
顺便说一句:我想知道问这样的问题背后的原因是什么。通常情况下,不需要关心这些细节。
串联操作不会创建那么多 String 对象。它创建一个 StringBuilder
然后附加字符串。所以可能有5个对象,
3(变量)+ 1(sb)+ 1(串联字符串)。
您问题的任何答案都取决于 JVM 实现和当前使用的 Java 版本。我认为在面试中问这个问题是不合理的。
Java 8
在我的机器上,使用 Java 1.8.0_201,您的代码片段生成此字节码
L0
LINENUMBER 13 L0
LDC "First"
ASTORE 1
L1
LINENUMBER 14 L1
LDC "Second"
ASTORE 2
L2
LINENUMBER 15 L2
LDC "Third"
ASTORE 3
L3
LINENUMBER 16 L3
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 3
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 4
证明正在创建 5 个对象(3 String
文字*,1 StringBuilder
, 1 dynamically produced String
instance by StringBuilder#toString
)。
Java 12
在我的机器上,Java 12.0.2,字节码是
// identical to the bytecode above
L3
LINENUMBER 16 L3
ALOAD 1
ALOAD 2
ALOAD 3
INVOKEDYNAMIC makeConcatWithConstants(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; [
// handle kind 0x6 : INVOKESTATIC
java/lang/invoke/StringConcatFactory.makeConcatWithConstants(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
// arguments:
"\u0001\u0001\u0001"
]
ASTORE 4
其中 magically 将 "the correct answer" 更改为 4 个对象,因为不涉及中间 StringBuilder
。
*让我们深入挖掘一下。
12.5. Creation of New Class Instances
A new class instance may be implicitly created in the following situations:
- Loading of a class or interface that contains a string literal (§3.10.5) may create a new String object to represent the literal. (This will not occur if a string denoting the same sequence of Unicode code points has previously been interned.)
也就是说,当你启动一个应用程序时,String池中已经有对象了。您几乎不知道它们是什么以及它们来自哪里(除非您扫描所有加载的 classes 以查找它们包含的所有文字)。
java.lang.String
class 无疑将作为基本 JVM class 加载,这意味着它的所有文字都将被创建并放入池中。
让我们从 String
的源代码中随机选择一个片段,从中挑选几个文字,在我们程序的最开始放置一个断点,并检查池中是否包含这些文字。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
...
public String repeat(int count) {
// ...
if (Integer.MAX_VALUE / count < len) {
throw new OutOfMemoryError("Repeating " + len + " bytes String " + count +
" times will produce a String exceeding maximum size.");
}
}
...
}
他们确实在那里。
作为一个有趣的发现,这个 IDEA 的过滤有一个副作用:我正在寻找的子字符串也被添加到池中。在我应用 this.contains("bytes String")
.
后,池大小增加了一个(添加了 "bytes String"
)
这会给我们留下什么?
我们不知道 "First"
在我们调用 String str1 = "First";
之前是否被创建和驻留,所以我们不能肯定地说该行创建了一个新实例。
一致的 Java 实现可以在 运行 时间或编译时以任意数量的方式连接字符串,需要任意数量的 运行 时间对象,包括零对象如果在 运行 次检测到不需要结果。
将在字符串常量池中创建4个字符串对象。 3 个用于文字,1 个用于连接。
如果我们使用
String s1 = new String("one")
它将创建两个对象,一个在常量池中,一个在堆内存中。
如果我们定义:
String s1 = "one";
String s2 = new String("one");
它将创建两个对象,一个在常量池中,一个在堆内存中。
我在一次采访中被问及将在给定问题上创建的对象数量:
String str1 = "First";
String str2 = "Second";
String str3 = "Third";
String str4 = str1 + str2 + str3;
我回答说在字符串池中会创建 6 个对象。
3 would be for each of the three variables.
1 would be forstr1 + str2
(let's saystr
).
1 would be forstr2 + str3
.
1 would be for thestr + str3
(str = str1 + str2
).
我给的答案正确吗?如果不是,正确答案是什么?
应该是5:
三个用于三个文字(分配给
str1
、str2
和str3
)一个
str1 + str2
一个给
(result from the previous operation) + str3
(分配给str4
)
Java 8 可能会创建 5 个对象:
- 3 代表 3 个文字
- 1
StringBuilder
- 1 表示串联
String
尽管 Java 9 things changed 和 String
连接不再使用 StringBuilder
。
根据给定的信息,无法明确回答问题。正如 JLS, §15.18.1 中所述:
... To increase the performance of repeated string concatenation, a Java compiler may use the
StringBuffer
class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.
这意味着答案至少取决于所使用的具体 Java 编译器。
我认为我们能做的最好的就是给出一个区间作为答案:
- 一个聪明的编译器可能能够推断出
str1
到str3
从未被使用过,并在编译期间折叠连接,这样只创建一个String
-object(一个被str4
) 引用
- 创建的
String
的最大合理数量应为 5:str1
到str3
各一个,tmp = str1 + str2
一个,[=19= 一个].
所以...我的答案是 "something between one to five String
-objects"。至于只为此操作创建的对象总数……我不知道。这也可能取决于例如StringBuffer
已实施。
顺便说一句:我想知道问这样的问题背后的原因是什么。通常情况下,不需要关心这些细节。
串联操作不会创建那么多 String 对象。它创建一个 StringBuilder
然后附加字符串。所以可能有5个对象,
3(变量)+ 1(sb)+ 1(串联字符串)。
您问题的任何答案都取决于 JVM 实现和当前使用的 Java 版本。我认为在面试中问这个问题是不合理的。
Java 8
在我的机器上,使用 Java 1.8.0_201,您的代码片段生成此字节码
L0
LINENUMBER 13 L0
LDC "First"
ASTORE 1
L1
LINENUMBER 14 L1
LDC "Second"
ASTORE 2
L2
LINENUMBER 15 L2
LDC "Third"
ASTORE 3
L3
LINENUMBER 16 L3
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 3
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 4
证明正在创建 5 个对象(3 String
文字*,1 StringBuilder
, 1 dynamically produced String
instance by StringBuilder#toString
)。
Java 12
在我的机器上,Java 12.0.2,字节码是
// identical to the bytecode above
L3
LINENUMBER 16 L3
ALOAD 1
ALOAD 2
ALOAD 3
INVOKEDYNAMIC makeConcatWithConstants(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; [
// handle kind 0x6 : INVOKESTATIC
java/lang/invoke/StringConcatFactory.makeConcatWithConstants(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
// arguments:
"\u0001\u0001\u0001"
]
ASTORE 4
其中 magically 将 "the correct answer" 更改为 4 个对象,因为不涉及中间 StringBuilder
。
*让我们深入挖掘一下。
12.5. Creation of New Class Instances
A new class instance may be implicitly created in the following situations:
- Loading of a class or interface that contains a string literal (§3.10.5) may create a new String object to represent the literal. (This will not occur if a string denoting the same sequence of Unicode code points has previously been interned.)
也就是说,当你启动一个应用程序时,String池中已经有对象了。您几乎不知道它们是什么以及它们来自哪里(除非您扫描所有加载的 classes 以查找它们包含的所有文字)。
java.lang.String
class 无疑将作为基本 JVM class 加载,这意味着它的所有文字都将被创建并放入池中。
让我们从 String
的源代码中随机选择一个片段,从中挑选几个文字,在我们程序的最开始放置一个断点,并检查池中是否包含这些文字。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
...
public String repeat(int count) {
// ...
if (Integer.MAX_VALUE / count < len) {
throw new OutOfMemoryError("Repeating " + len + " bytes String " + count +
" times will produce a String exceeding maximum size.");
}
}
...
}
他们确实在那里。
作为一个有趣的发现,这个 IDEA 的过滤有一个副作用:我正在寻找的子字符串也被添加到池中。在我应用 this.contains("bytes String")
.
"bytes String"
)
这会给我们留下什么?
我们不知道 "First"
在我们调用 String str1 = "First";
之前是否被创建和驻留,所以我们不能肯定地说该行创建了一个新实例。
一致的 Java 实现可以在 运行 时间或编译时以任意数量的方式连接字符串,需要任意数量的 运行 时间对象,包括零对象如果在 运行 次检测到不需要结果。
将在字符串常量池中创建4个字符串对象。 3 个用于文字,1 个用于连接。
如果我们使用
String s1 = new String("one")
它将创建两个对象,一个在常量池中,一个在堆内存中。
如果我们定义:
String s1 = "one";
String s2 = new String("one");
它将创建两个对象,一个在常量池中,一个在堆内存中。