我可以用两种方式实现单一方法,
的 1
public String getTestMessage()
{
return "Hello" + "World..!";
}
2
public String getTestMessage()
{
return new StringBuffer("Hello").append("World..!").toString();
}
在第一个场景中,将创建两个新的String
个对象。
在第二个场景中,还会创建两个新对象,但会有一个String
和StringBuffer
。现在方式会很快吗?我有点困惑。
答案 0 :(得分:5)
在你的两个场景之间,选项1每次都会更快,除非JITC做了我不期望的事情。不过,我确实不希望它有所作为,除非你非常频繁地调用这些方法。
为什么呢?因为您实际上没有使用选项1创建新对象。编译器应该执行常量折叠,将"Hello" + "World..!"
转换为"HelloWorld..!"
。因为这是一个编译时常量String
,所以它会在VM启动时自动插入String
池。因此,每次调用该方法时,您只需参考该标准" canonical" String
。不执行任何对象创建。
在选项2中,您始终会创建多个对象 - StringBuffer
(顺便说一下,您应使用StringBuilder
),支持char[]
和结果{{1 (至少)。并且在紧密循环中执行此操作效率不高。
此外,选项1更具可读性,在编写代码时应始终考虑这一点。
证明:
鉴于此测试代码:
String
使用public class Test {
public String getTestMessageA() {
return "Hello" + "World..!";
}
public String getTestMessageB() {
return new StringBuffer("Hello").append("World..!").toString();
}
}
进行编译,向我们展示了在编译为字节码之前处理此代码的内容:
javac -XD-printflat
注意public class Test {
public Test() {
super();
}
public String getTestMessageA() {
return "HelloWorld..!";
}
public String getTestMessageB() {
return new StringBuffer("Hello").append("World..!").toString();
}
}
如何在编译时将转换为单 "Hello" + "World..!"
。所以String
串联不第一个选项中发生了什么。
现在让我们看看字节码。这是常量池:
String
这里是选项1的字节码:
Constant pool:
#1 = Methodref #10.#20 // java/lang/Object."<init>":()V
#2 = String #21 // HelloWorld..!
#3 = Class #22 // java/lang/StringBuffer
#4 = String #23 // Hello
#5 = Methodref #3.#24 // java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
#6 = String #25 // World..!
#7 = Methodref #3.#26 // java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
#8 = Methodref #3.#27 // java/lang/StringBuffer.toString:()Ljava/lang/String;
嗯,这很简短。如您所见,JVM从池中加载常量( public java.lang.String getTestMessageA();
Code:
0: ldc #2 // String HelloWorld..!
2: areturn
)并返回它。没有直接在方法中创建对象。
现在这里是选项2的字节码:
ldc
因此,此代码创建一个新的public java.lang.String getTestMessageB();
Code:
0: new #3 // class java/lang/StringBuffer
3: dup
4: ldc #4 // String Hello
6: invokespecial #5 // Method java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
9: ldc #6 // String World..!
11: invokevirtual #7 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
14: invokevirtual #8 // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
17: areturn
,从字符串池中加载适当的常量,为每个常量调用StringBuffer
方法,然后在缓冲区上调用append()
方法。 toString()
实现如下:
toString()
因此,选项2将在每次调用时创建两个新对象,并执行更多指令。因此,选项1会更快。
答案 1 :(得分:5)
此
"Hello" + "World..!"
是一个常量表达式。编译器将执行连接并将其作为单个String
常量添加到字节代码中。因此,JVM只为它创建一个String
对象。
在
return new StringBuffer("Hello").append("World..!").toString();
有两个String
文字。 JVM将为每个创建一个对象。然后,它将创建一个StringBuffer
对象和所有相应的后备char[]
,最后创建String
方法的toString()
对象返回值。
答案 2 :(得分:1)
我很想知道我遇到this article后面的场景,这可以证明我们完全错了。
文章说 +
运算符看似无辜,但生成的字节码会产生一些惊喜。使用StringBuffer
进行连接实际上可以生成比使用String
要快得多的代码。
由String
连接生成的字节码创建一个StringBuffer
对象,然后调用其append
方法。在StringBuffer
对象上执行连接后,它必须是转换回String
。这是通过调用toString
方法完成的。此方法从临时String
对象创建新的StringBuffer
对象。
总之,String conactenation inturn创建了三个对象:
一个String对象, 一个StringBuffer对象, 一个String对象。
但是,在第二种情况下无需创建临时StringBuffer
。
请参阅article link获取完整信息
答案 3 :(得分:0)
性能方面,StringBuffer在执行连接时速度更快。这是因为当你连接一个String时,你每次都在内部创建一个新对象,因为String是不可变的。
对于较小的字符串(附加一个或两个字符串)第一种方法没有问题,考虑一下这会降低性能。
来到这种情况:
如果您添加
"value" + 5
Java必须首先将5转换为String。如果你看一下这个字节代码,Java实际上会调用String.valueOf(5)。查看该方法,您会发现Java创建了一个char数组和一个String(2个对象)来构建该值。
当Java追加5时,它只在StringBuilder上调用append(int)。该方法只是将值复制到内部字符缓冲区而不创建任何额外的对象。
内部追加
if (str == null) str = "null";
int len = str.length();
if (len == 0) return this;
int newCount = count + len;
if (newCount > value.length)
expandCapacity(newCount);
str.getChars(0, len, value, count);
count = newCount;
return this;
这使用本机方法System.arrayCopy(),这里是http://www.cafeaulait.org/course/week2/50.html
请参阅此What is the difference between String and StringBuffer in Java?