面试官又整新活,竟然问我for循环用i++和++i哪一个效率高?

2021年11月25日 阅读数:2
这篇文章主要向大家介绍面试官又整新活,竟然问我for循环用i++和++i哪一个效率高?,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

原创:微信公众号 码农参上,欢迎分享,转载请保留出处。java

前几天,一个小伙伴告诉我,他在面试的时候被面试官问了这么一个问题:面试

在for循环中,到底应该用 i++ 仍是 ++i ?微信

听到这,我感受这面试官确实有点不按套路出牌了,放着好好的八股文不问,净整些幺蛾子的东西。在临走的时候,小伙伴问面试官这道题的答案是什么,面试官没有明确告诉答案,只是说让从程序执行的效率角度本身思考一下。数据结构

好吧,既然这个问题被抛了出来,那咱们就见招拆招,也给之后面试的小伙伴们排一下坑。jvm

思路

前面提到,这个搞事情的面试官说要从执行效率的角度思考,那咱们就抛开语义上的区别,从运行结果之外的效率来找找线索。回想一下,咱们在之前介绍CAS的文章中提到过,后置自增i++和前置自增++i都不是原子操做,那么实际在执行过程当中是什么样的呢?下面,咱们从字节码指令的角度,从底层进行一波分析。ide

i++ 执行过程

先写一段简单的代码,核心功能就只有赋值和自增操做:测试

public static void main(String[] args) {
    int i=3;
    int j=i++;
    System.out.println(j);
}

下面用javap对字节码文件进行反编译,看一下实际执行的字节码指令:优化

是否是有点难懂?不要紧,接下来咱们用图解的形式来直观地看看具体执行的过程,也帮你们解释一下晦涩的字节码指令是如何操做栈帧中的数据结构的,为了简洁起见,在图中只列出栈帧中比较重要的操做数栈局部变量表idea

上面的代码中除去打印语句,总体能够拆分红两步,咱们先看第一步 int i=3 是如何执行的 。翻译

上面两条操做数栈和局部变量表相关的字节码指令仍是比较容易理解的,下面再看一下第二步int j=i++的执行过程:

在上图中须要注意的是,iinc可以直接更新局部变量表中的变量值,它不须要把数值压到操做数栈中就可以直接进行操做。在上面的过程当中,抛去赋值等其余操做,i++实际执行的字节码指令是:

2: iload_1
3: iinc    1, 1

若是把它翻译成咱们能看懂的java代码,能够理解为:

int temp=i;
i=i+1;

也就是说在这个过程当中,除了必须的自增操做之外,又引入了一个新的局部变量,接下来咱们再看看++i的执行过程。

++i 执行过程

咱们对上面的代码作一点小小的改动,仅把i++换成++i,再来分析一下++i的执行过程是怎样的。

public static void main(String[] args) {
    int i=3;
    int j=++i;
    System.out.println(j);
}

一样,用javap反编译字节码文件:

int i=3对应前两行字节码指令,执行过程和前面i++例子中彻底相同,能够忽略不计,重点仍是经过图解的方式看一下int j=++i对应的字节码指令的执行过程:

抛去赋值操做,++i实际执行过程只有一行字节码指令:

2: iinc    1, 1

转换成能理解的java代码的话,++i实际执行的就在局部变量中执行的:

i=i+1;

这么看来,在使用++i时确实比i++少了一步操做,少引入了一个局部变量,若是在运算结果相同的场景下,使用++i的话的确效率会比i++高那么一点点。

那么回到开头的问题,两种自增方式应用在for循环中执行的时候,那种效率更高呢?刚才得出的结论仍然适用于for循环中吗,别急,让咱们接着往下看。

for循环中的自增

下面准备两段包含了for循环的代码,分别使用i++后置自增和++i前置自增:

//i++ 后置自增
public class ForIpp {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
        }
    }
}
//++i 前置自增
public class ForPpi {
    public static void main(String[] args) {
        for (int i = 0; i < 5; ++i) {
            System.out.println(i);
        }
    }
}

老规矩,仍是直接反编译后的字节码文件,而后对比一下指令的执行过程:

到这里,有趣的现象出现了,两段程序执行的字节码指令部分竟然如出一辙。先不考虑为何会有这种现象,咱们仍是经过图解来看一下字节码指令的执行过程:

能够清晰的看到,在进行自增时,都是直接执行的iinc,在以前并无执行iload的过程,也就是说,两段代码执行的都是++i。这一过程的验证其实还有更简单的方法,直接使用idea打开字节码文件,就能够看到最终for循环中使用的相同的前置自增方式。

那么,为何会出现这种现象呢?归根结底,仍是java编译器对于代码的优化,在两种自增方式中,若是没有赋值操做,那么都会被优化成一种方式,就像下面的两个方法的代码:

void ipp(){
    int i=3;
    i++;
}
void ppi(){
    int i=3;
    ++i;
}

最终执行时的字节码指令都是:

0: iconst_3
1: istore_1
2: iinc    1, 1
5: return

能够看到,在上面的这种特定状况下,代码通过编译器的优化,保持了语义不变,并经过转换语法的形式提升了代码的运行效率。因此再回到咱们开头的问题,就能够得出结论,在for循环中,经过jvm进行编译优化后,不管是i++仍是++i,最终执行的方式都是++i,所以执行效率是相同的。

因此,之后再碰到这种半吊子的面试官,和你谈for循环中i++++i的效率问题,自信点,直接把答案甩在他的脸上,两种方式效率同样!

本文代码基于Java 1.8.0_261-b12 版本测试

做者简介,码农参上,一个热爱分享的公众号,有趣、深刻、直接,与你聊聊技术。我的微信DrHydra9,欢迎添加好友,进一步交流。