3

对于较新的编译器,我发现自己正在尝试编写更易于阅读的代码,但如果我希望在后台完成的优化实际上并没有完成,则可能会占用更多内存。以这段代码为例,非常简单的案例

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 对象吗?我希望不是 ...

4

6 回答 6

7

你无法逃避String被创造的东西。您将其分配给局部变量的事实在这里无关紧要,实际上在字节码级别甚至不会注意到这一事实:在该级别,无论有或没有显式变量,对结果的引用都必须放在堆栈中为了被传递到链中的下一个方法调用。

您认为String创建了不必要的实例的想法可能源于来自另一种语言(例如 C)的本能:赋值String s = ...仅复制对唯一一个字符串实例的引用。这是因为所有 Java 对象都驻留在堆上,因此您总是需要显式复制一个对象以实际涉及另一个实例。例如,如果你写了String line = new String(scanner.nextLine()),那确实会创建一个不必要的String.

总而言之,您的代码的任何版本都没有涉及优化,因此仅根据风格偏好进行选择。

于 2012-11-09T14:03:54.017 回答
7

一些一般原则:

  • 过早的优化毫无意义
  • 在这些次要情况下保存可读性是没有意义的
  • 大多数时候优化来自不断变化的算法及其复杂性
  • 在尝试优化不是算法的东西时,你永远不是一个好的编译器
  • 为了确保任何优化,只有两种方法:查看字节码或基准性能,其他一切通常都是猜测

在您的特定情况下:变量声明在优化方面不会改变任何内容,因为在这两种情况下,字符串都由堆栈进行实例化nextLine()并放置在堆栈上,将其分配给一个变量(它在字节码中消失,除非它是一个实例变量,如它的用处只是为了你的眼睛)不会改变任何东西。

于 2012-11-09T14:05:10.647 回答
2

为什么不问Java 类文件反汇编器-javap每个 JDK 中包含的程序?

具有以下源代码:

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它不会再创建任何对象,它只会再创建一个对以任一方式分配的对象的引用

于 2012-11-09T14:21:07.760 回答
2

似乎离题了,但这也会提高性能。

while (!result && scanner.hasNextLine())
{
    String line = scanner.nextLine();
    result = line.indexOf(searchString) >= 0;
}
于 2012-11-09T14:37:11.970 回答
1

回答您的问题,何时scanner.nextLine().indexOf(searchString)执行;你期望nextLine()做什么?您希望在哪个对象indexOf()上执行?

正如您可能已经猜到的那样,它依赖于String; 所以是的,这String是创建的,是的,这String是使用的。这是(与您的猜测相反)必要的。

与对象的实例化( )相比,声明变量(String s)和赋值的操作没有任何成本new String("test")

换句话说,您试图实现的目标既没有用,也没有显着提高性能。


这里还有第二个问题,一般来说对开发人员来说是一个更深层次的问题。在没有遇到实际问题并且没有任何明显迹象表明该代码可能使您的应用程序运行显着变慢的情况下尝试对此类代码进行优化还为时过早

大多数情况下,它会分散您对想要实现的目标的注意力,并会导致您为了优化而编写不可读的代码(甚至可能根本不是优化!)。

在你的特殊情况下(我很惊讶之前没有人提到它,这就是我写这个答案的原因)你的“优化”代码会让每个人的生活变得更糟。

想象一下,您的代码运行,并且在某些时候一切都失败了,您NullPointerException在这一行得到了一个:

    result = scanner.nextLine().indexOf(searchString) >= 0;

现在您必须手动调试该代码以查明是否scannernull或是否由于某种原因nextLine()返回,而不是清楚地了解失败的原因null

这个问题甚至在你以前的代码中都不存在,但是这种尽早优化并试图使你的代码更紧凑以避免浪费一些操作的愿望现在使你的代码在全球范围内变得更糟。

于 2012-11-09T14:22:26.540 回答
0

编译器无论如何都会内联局部变量行,所以两者没有区别

于 2012-11-09T14:04:34.150 回答