前几天有个小伙伴告诉我,他在面试的时候被面试官问了这样一个问题:在for循环中,应该用i++还是++i?听到这话,感觉面试官打牌真的有点太过分了,把好的八股文搁置一旁,收拾一些无聊的东西。临走前,小伙伴问面试官这个问题的答案是什么。面试官没有说清楚答案,只是让他从程序执行效率的角度考虑一下。好吧,既然这个问题抛出来了,那就看看我们能做些什么吧,同时也会为以后的面试伙伴安排陷阱。前面在思路中提到,做事的面试官说要从执行效率的角度去思考,那么我们抛开语义上的差异,从运行结果以外的效率上寻找线索。回想一下我们在上一篇介绍CAS的文章中提到后自增i++和前自增++i不是原子操作,那么实际的执行过程是怎样的呢?接下来,我们从word段代码指令的角度,从底层进行一波分析。在i++执行过程中,先写一段简单的代码,核心功能只有赋值和自增操作:publicstaticvoidmain(String[]args){inti=3;intj=i++;System.out.println(j);}下面使用javap反编译字节码文件,看看实际执行的字节码指令:是不是有点难懂?没关系。下面我们就用图形的形式直观的看下具体的执行过程,也帮大家解释一下比较晦涩的。字节码指令如何操作栈帧中的数据结构。为了简洁起见,图中只列出了栈帧中比较重要的操作数栈和局部变量表。上面的代码除了打印语句外,整体可以分为两步。我们先看看第一步inti=3是怎么执行的。上面两个操作数栈和局部变量表相关的字节码指令都比较容易理解。我们看一下第二步intj=i++的执行过程:上图中需要注意的是,iinc可以直接更新局部变量表中的变量值可以直接操作,不需要将值压入操作数栈。上述过程中,抛开赋值等其他操作不谈,i++实际执行的字节码指令为:2:iload_13:iinc1,1如果翻译成我们能看懂的java代码,可以理解为:inttemp=i;我=我+1;也就是说,在这个过程中,除了必要的自增操作外,还引入了一个新的局部变量。接下来我们看一下++i的执行过程。++i的执行过程我们对上面的代码做一个小改动,只是把i++换成++i,然后分析++i的执行过程。publicstaticvoidmain(String[]args){inti=3;intj=++i;System.out.println(j);}同样用javap反编译字节码文件:inti=3对应字节码前两行该指令的执行过程与前面i++例子完全一样,可以忽略。重点是用图形化的方式看下intj=++i对应的字节码指令的执行过程:丢掉赋值操作,++i实际执行的过程只有一行字节码指令:2:iinc1,1如果转换成可以理解的java代码,++i的实际执行是在局部变量中执行的:i=i+1;看来在使用++的时候确实比i++少了一步,少引入了一个局部变量。如果运算结果相同,使用++i的效率会比使用i++高一点。那么回到一开始的问题,在for循环中应用两种自增方式时,哪种方式效率更高呢?刚刚得出的结论是否还适用于for循环?别着急,我们继续往下看。for循环中的自增准备两段包含for循环的代码,分别使用i++后自增和++i前自增://i++后自增publicclassForIpp{publicstaticvoidmain(String[]args){for(inti=0;i<5;i++){System.out.println(i);}}}//++i预自增publicclassForPpi{publicstaticvoidmain(String[]args){for(inti=0;i<5;++i){System.out.println(i);}}}老规矩是直接反编译字节码文件,然后对比指令的执行过程:这里,出现了一个有趣的现象是的,字节码指令两个程序执行的是完全一样的。不管为什么会出现这种现象,我们先通过图来看一下字节码指令的执行过程:可以清楚的看到,在进行自增时,直接执行了iinc,而之前并没有执行iload的过程,即也就是说,这两段代码的执行都是++i。事实上,有一种更简单的方法可以验证这个过程。直接用idea打开字节码文件,可以看到最后的for循环使用的同样的预自增方法。那么,为什么会出现这种现象呢?说到底还是java编译器对代码的优化。两个自增方法中,如果没有赋值操作,会优化成一个方法,就像下面两个方法一样。代码:voidipp(){inti=3;i++;}voidppi(){inti=3;++i;}最后执行的字节码指令都是:0:iconst_31:istore_12:iinc1,15:return可以看到在上面的具体案例中,编译器对代码进行了优化,保持语义不变,通过语法形式的转换来提高代码的效率。那么回到我们一开始的问题,我们可以得出结论,在for循环中,经过jvm编译优化后,无论是i++还是++i,最终的执行方式都是++i,所以执行效率就是相同的。所以,以后遇到这种半吊子面试官,和你说for循环中i++和++i的效率的时候,自信一点,直接把答案扔在他脸上。两种方法的效率是一样的!本文代码基于Java1.8.0_261-b12版本测试
