我知道String是不可变的,以及何时使用StringBuilder或StringBuffer。我还读到这两个片段的字节码最终会相同:
//Snippet 1
String variable = "text";
this.class.getResourceAsStream("string"+variable);
//Snippet 2
StringBuilder sb = new StringBuilder("string");
sb.append("text");
this.class.getResourceAsStream(sb.toString());
但我显然有些不对劲。在eclipse中通过Snippet 1进行调试时,实际上我被带到StringBuilder构造函数和append方法。我想我错过了有关如何解释字节码以及调试器如何引用源代码中的行的详细信息;如果有人能解释一下,我真的很感激。另外,也许你可以指出什么是JVM特定的和什么不是(我是运行Oracle的v6),谢谢!
答案 0 :(得分:8)
为什么在调试字符串连接时会弹出StringBuilders?
因为字符串连接(通过'+'运算符)通常编译为使用StringBuffer
或StringBuilder
进行连接的代码。 JLS明确允许这种行为。
“实现可以选择一步执行转换和连接,以避免创建然后丢弃中间String对象。为了提高重复字符串连接的性能,Java编译器可以使用StringBuffer类或类似的减少通过评估表达式创建的中间String对象数量的技术。“ JLS 15.18.1。
(如果您的代码使用的是StringBuffer
而不是StringBuilder
,可能是因为它是使用非常旧的Java编译器编译的,或者是因为您指定了一个非常旧的目标JVM。 StringBuilder
类是Java的相对补充。旧版本的JLS曾经提到StringBuffer
而不是StringBuilder
,IIRC。)
此外,也许您可以指出JVM的具体内容和不具备的内容。
为"string" + variable"
生成的字节码取决于Java编译器如何处理串联。 (事实上,所有生成的字节码都在某种程度上依赖于Java编译器.JLS和JVM规范没有规定必须生成什么字节码。规范更多地是关于程序应该如何表现,以及单个字节码的作用。)
@supercat评论:
我想知道为什么字符串连接不会使用例如一个String构造函数重载,接受两个String对象,分配一个适当组合大小的缓冲区,并加入它们?或者,当连接更多字符串时,带有String []的重载?创建包含对要连接的字符串的引用的String []应该不比创建StringBuilder更昂贵,并且能够一次创建一个完美大小的后备存储应该是一个轻松的性能获胜。
也许......但我可能会说不是。这是一个涉及复杂权衡的复杂领域。所选择的字符串连接实现策略必须适用于各种不同的用例。
我的理解是,在查看了许多方法之后选择了原始策略,并进行了一些大规模的静态代码分析和基准测试,以试图找出哪种方法最好。我想象他们考虑了你提出的所有替代方案。 (毕竟,他们是聪明人......)
话虽如此,您可以使用Java 6,7和8的完整源代码库。这意味着您可以下载它,并尝试自己的一些实验,看看您的理论是否正确。如果他们......并且你可以收集他们的确凿证据......然后向OpenJDK团队提交补丁。
答案 1 :(得分:1)
@StephenC我仍然不相信这个解释。编译器可以做它想要做的任何优化,但是当你通过eclipse进行调试时,源代码视图对编译器代码是隐藏的,它不应该将一段代码跳转到同一源文件中的另一段代码。
问题中的以下描述表明源代码和字节代码不同步。即,他没有运行最新的代码。
When debugging through Snippet 1 in eclipse, I am actually taken to the StringBuffer constructor and to the append method
和
how the debugger refers back to the lines in the source code