5

我目前正在学习用 Java 编程,我有一个关于标题中列出的一元增量器的问题,我无法在其他地方找到。我刚开始玩它们,并不能完全决定在 for 循环中使用哪一个,因为似乎前缀(递增然后评估)和后缀(评估然后递增)之间的行为差​​异不适用于 for陈述。所以我两种都试了,都奏效了。这让我很担心,因为我想以它们应该被使用的方式使用它们。

所以我的问题是,它们在增加 for 循环时是否真的可以互换,或者是否有一些模糊的问题我会在使用一个副的过程中遇到另一个问题?

我决定给它们计时(如下),并且++x肯定比 运行得快x++,但我不知道为什么。任何人都可以扩展吗?

谢谢!

public class PlusPlus
{
    public static void main(String[] args)
    {
        long startTime1, startTime2, endTime1, endTime2;
        final double COUNT = 100000000;

        //times x++ incrementing
        startTime1 = System.currentTimeMillis();
        for(int x = 0; x < COUNT; x++);
        endTime1 = System.currentTimeMillis();
        System.out.println("x++ loop: " + (endTime1 - startTime1) + " milliseconds");

        //times ++x incrementing
        startTime2 = System.currentTimeMillis();
        for(int x = 0; x < COUNT; ++x);
        endTime2 = System.currentTimeMillis();
        System.out.println("++x loop: " + (endTime2 - startTime2) + " milliseconds");
    }
}
4

4 回答 4

5

在了解性能如何工作方面,您的测试不会产生太多效果。由于 JVM 的性质,编写性能测试非常困难,以至于编写了整个框架来进行基准测试。

要真正了解区别是什么,您应该使用javap.

我编译了这个类:

public class MyClass {

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++)
            System.out.println(i);
    }
}

并跑去javap -c MyClass拿这个main

public static void main(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   bipush  20
   5:   if_icmpge       21
   8:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   11:  iload_1
   12:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   15:  iinc    1, 1
   18:  goto    2
   21:  return

}

然后我又做了一次,但这一次,使用了预增量:

for (int i = 0; i < 20; ++i)

我得到了这个javap

public static void main(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   bipush  20
   5:   if_icmpge       21
   8:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   11:  iload_1
   12:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   15:  iinc    1, 1
   18:  goto    2
   21:  return

}

注意到什么了吗?特别是它们完全相同?

这是因为 Java 优化了关于预加载和后加载值的额外指令。它认识到它生成的将变量加载到寄存器(iload_#javap输出中)的指令是多余的和不必要的,并对其进行了优化。

在编程中,有一个术语叫做过早优化。这是您优化某些您并不真正需要优化的地方,因为它可能会导致性能问题。你真正要做的就是让你的代码更复杂。大多数“优秀”程序员(如果存在这样的存在)会避免这种情况,而是专注于编写可维护的代码。如果可维护代码表现不佳,对其进行优化。我记得在 SO 上阅读了关于 3 个不同级别的程序员(初级、中级、高级)以及如何(粗略地)判断您属于哪个类别的回复。我看看能不能找到。

编辑:我的答案的快速解释:javac将你的 .java 文件编译成字节码。字节码存储在 .class 文件中。字节码一步一步地告诉 JVM 如何完成某事。例如,iinc 1, 1告诉它获取变量1(在本例中为 this i)并将其递增 1。iload_1告诉它获取变量1并加载它以便可以使用它,通常在操作或方法调用中。例如,这个:

   8:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   11:  iload_1
   12:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V

意思是“获取System.out并加载它,然后加载变量#1 ( i),现在调用方法#3。” 当我们调用方法时,首先加载的(System.out)是我们的“目标”,意思是调用那个人的方法。加载的所有其他内容都作为参数传递。在这种情况下,这意味着i。所以那三条线代表线System.out.println(i)。字节码只是告诉 Java 要做什么才能真正做到这一点。

反编译的代码中没有iload_1前增量和后增量的事实意味着 Java 对其进行了优化,因为它意识到它实际上不会被使用。在这两种情况下,它只是没有iinciload这与该线程中的大多数其他答案完全相反。

于 2013-09-25T23:46:44.947 回答
3

就效率而言,我们正在分裂头发。即使您真的尝试优化您的金属代码,x++vs++x也不是超级重要。但是,是的,++x在技术上更快,因为x++必须存储 x 的旧值,递增它,然后返回旧值。++x不需要存储它。

担心++xvs的主要原因x++实际上是返回值。有时你想要一个胜过另一个。

于 2013-09-25T23:19:05.643 回答
2

为了返回“旧”值,x++操作员需要对旧值进行临时复制,导致性能略有下降。

在您的for循环中,您没有使用返回的值,因此++x更可取。

于 2013-09-25T23:17:49.367 回答
1

++x:增加x;整体表达式的值是自增后的值

x++:增加 x;整体表达式的值是增量前的值

考虑这两个部分:

int x = 0;
System.out.println(x++); // Prints 0
// x is now 1

int y = 0;
System.out.println(++y); // Prints 1
// y is now 1

我个人尽量避免在更大的语句中使用它们作为表达式 - 我更喜欢独立代码,如下所示:

int x = 0;
System.out.println(x); // Prints 0
x++;
// x is now 1


int y = 0;
y++;
System.out.println(y); // Prints 1
// y is now 1

在这里,我相信每个人都可以计算出打印的内容以及 x 和 y 的最终值,而不会太摸不着头脑。

有时在表达式中提供前/后增量是很有用的,但首先要考虑可读性。

于 2013-09-25T23:21:35.223 回答