对于较新的编译器,我发现自己试图编写更容易阅读的代码,但如果我希望在引擎盖下完成的优化实际上并没有完成,那么可能会有更多的内存需求。 以此代码为例,非常简单的案例
while (scanner.hasNextLine() && !result)
{
String line = scanner.nextLine();
result = line.indexOf(searchString) >= 0;
}
假设(使用Eclipse Juno,Java 7)生成与
相同的字节代码是否公平?while (scanner.hasNextLine() && !result)
{
result = scanner.nextLine().indexOf(searchString) >= 0;
}
前者虽然2行代码减少了第二行的长度并使眼睛更容易。恕我直言 但它是否也会导致创建一个无关的String对象?我希望不是......
答案 0 :(得分:7)
您无法逃避正在创建的String
。将它分配给局部变量这一事实在这里是无关紧要的,实际上在字节码级别,这个事实甚至不会被注意到:在该级别,无论是否有显式变量,对结果的引用必须放在堆栈中要传递给链中的下一个方法调用。
您认为创建不必要的String
实例的想法可能源于来自其他语言的本能,例如C:作业String s = ...
仅将引用复制到唯一的字符串实例。这是因为所有Java对象都驻留在堆上,因此您始终需要显式复制对象以实际涉及另一个实例。例如,如果你写了String line = new String(scanner.nextLine())
,那确实会创建一个不必要的String
实例。
总而言之,您的代码的任何版本都不涉及优化,因此请仅根据样式首选项进行选择。
答案 1 :(得分:7)
一些一般原则:
在您的特定情况下:变量声明在optimitation方面不会改变任何内容,因为在两种情况下,字符串都由nextLine()
实现并放在堆栈上,将其分配给变量(在字节码中消失) ,除非它是一个实例变量,因为它的用处仅适用于你的眼睛)不会改变任何东西。
答案 2 :(得分:2)
为什么不询问 Java类文件反汇编程序 - 每个JDK中包含的javap
程序?
拥有以下源代码:
public class Foo {
static void m1(Scanner scanner, String searchString, boolean result) {
while (scanner.hasNextLine() && !result) {
String line = scanner.nextLine();
result = line.indexOf(searchString) >= 0;
}
}
static void m2(Scanner scanner, String searchString, boolean result) {
while (scanner.hasNextLine() && !result) {
result = scanner.nextLine().indexOf(searchString) >= 0;
}
}
}
运行反汇编程序时:
javap -c Foo.class
您将获得以下字节码:
static void m1(java.util.Scanner, java.lang.String, boolean);
Code:
0: goto 22
3: aload_0
4: invokevirtual #33 // Method java/util/Scanner.nextLine:()Ljava/lang/String;
7: astore_3
8: aload_3
9: aload_1
10: invokevirtual #39 // Method java/lang/String.indexOf:(Ljava/lang/String;)I
13: iflt 20
16: iconst_1
17: goto 21
20: iconst_0
21: istore_2
22: aload_0
23: invokevirtual #45 // Method java/util/Scanner.hasNextLine:()Z
26: ifeq 33
29: iload_2
30: ifeq 3
33: return
static void m2(java.util.Scanner, java.lang.String, boolean);
Code:
0: goto 20
3: aload_0
4: invokevirtual #33 // Method java/util/Scanner.nextLine:()Ljava/lang/String;
7: aload_1
8: invokevirtual #39 // Method java/lang/String.indexOf:(Ljava/lang/String;)I
11: iflt 18
14: iconst_1
15: goto 19
18: iconst_0
19: istore_2
20: aload_0
21: invokevirtual #45 // Method java/util/Scanner.hasNextLine:()Z
24: ifeq 31
27: iload_2
28: ifeq 3
31: return
如果您比较两种方法的字节码,您会发现唯一差异是m1
包含以下两条额外说明:
7: astore_3
8: aload_3
这只是将对堆栈顶部对象的引用存储到局部变量中,没有别的。
修改强>
反汇编程序还可以显示方法的局部变量数:
javap -l Foo.class
哪个输出:
static void m1(java.util.Scanner, java.lang.String, boolean);
LocalVariableTable:
Start Length Slot Name Signature
0 34 0 scanner Ljava/util/Scanner;
0 34 1 searchString Ljava/lang/String;
0 34 2 result Z
8 14 3 line Ljava/lang/String;
static void m2(java.util.Scanner, java.lang.String, boolean);
LocalVariableTable:
Start Length Slot Name Signature
0 32 0 scanner Ljava/util/Scanner;
0 32 1 searchString Ljava/lang/String;
0 32 2 result Z
}
这基本上证实了上面唯一的区别 - m1
方法只分配了一个局部变量 - String line
。 它不会再创建任何对象,它只会再创建一个对分配方式的对象的引用。
答案 3 :(得分:2)
看似偏离主题,但这也会带来表现。
while (!result && scanner.hasNextLine())
{
String line = scanner.nextLine();
result = line.indexOf(searchString) >= 0;
}
答案 4 :(得分:1)
要回答您的问题,请执行scanner.nextLine().indexOf(searchString)
;你期望nextLine()
做什么?你希望在哪个对象上执行indexOf()
?
你可能已经猜到它依赖于String
;是的,这个String
被创建了,是的,这个String
被使用了。它(与您的猜测相反)必要。
声明变量(String s
)并分配值的操作与对象(new String("test")
)的实例化相比没有任何成本。
换句话说,你想要达到的目标既不是有用的,也不是更有效的。
这里存在第二个问题,对于开发人员而言,这是一个更深层次的问题。尝试对这类代码进行优化,没有遇到实际问题,并且没有任何明显迹象表明此代码可能会使您的应用程序运行速度明显变慢,只是过早。
大多数情况下,它会分散您想要实现的目标,并且会导致您为了优化而编写不可读的代码(甚至可能根本不是优化!)。
在你的特殊情况下(我很惊讶之前没有人提到它,这就是我写这个答案的原因)你的“优化”代码会让每个人的生活变得更糟。
想象一下,您的代码会运行,并且某些内容都会失败,并且您会在此行中获得NullPointerException
:
result = scanner.nextLine().indexOf(searchString) >= 0;
现在必须手动调试该代码,以确定scanner
是null
还是由于某种原因nextLine()
返回{{1>而不是清楚地了解失败的原因}}
这个问题在您之前的代码中甚至不存在,但是这种早期优化和尝试使代码更紧凑以避免等待一些操作的愿望现在使您的代码全局变得更糟。
答案 5 :(得分:0)
编译器无论如何都会内联局部变量行,因此两者之间没有区别