今天看到一道面试题,i++和++i的效率谁高谁低。

面试题的答案是++i要高一点。

我在网上搜了一圈儿,发现很多回答也都是同一个结论。

如果早个几年,我也会认同这个看法,但现在我负责任的说,这个结论是错的。

i++和++i的效率完全一致,包括i+=1,i=i+1,这几个的效率,完全一致。

来看一段源码:

大学生就业培训,高中生培训,在职人员转行培训,企业团训

    public void test1() {        int i = 0;        int x = i++;
        System.out.println(x);
    }    public void test2() {        int i = 0;        int x = ++i;
        System.out.println(x);
    }    public void test3() {        int i = 0;
        i += 1;        int x = i;
        System.out.println(x);
    }    public void test4() {        int i = 0;
        i = i + 1;        int x = i;
        System.out.println(x);
    }

大学生就业培训,高中生培训,在职人员转行培训,企业团训

四个方法里,同时定义了一个i和一个x,共计两个变量。执行的操作,也都是为i增加1,存入到x里。

在字节码里,这四个方法的执行过程,都是14条指令。

大学生就业培训,高中生培训,在职人员转行培训,企业团训 View Code

核心的执行过程同为6条指令,且指令性质一致,到了这里,可以得出结论,它们的效率是完全一致的。

 

光是知道这一点,就足够了吗?

重点从来就不是问题本身,而是问题背后涵盖的基础知识——如何读懂java字节码。

以test1方法为例:

大学生就业培训,高中生培训,在职人员转行培训,企业团训

// Method descriptor #6 ()V  // Stack: 2, Locals: 3
  public void test1();     0  iconst_0     1  istore_1 [i]     2  iload_1 [i]     3  iinc 1 1 [i]     6  istore_2 [x]     7  getstatic java.lang.System.out : java.io.PrintStream [15]    10  iload_2 [x]    11  invokevirtual java.io.PrintStream.println(int) : void [21]    14  return
      Line numbers:
        [pc: 0, line: 3]
        [pc: 2, line: 4]
        [pc: 7, line: 5]
        [pc: 14, line: 6]
      Local variable table:
        [pc: 0, pc: 15] local: this index: 0 type: DialogTest
        [pc: 2, pc: 15] local: i index: 1 type: int
        [pc: 7, pc: 15] local: x index: 2 type: int

大学生就业培训,高中生培训,在职人员转行培训,企业团训

看着很容易让人迷惑,首先需要记住两个概念:

1、operand stack 操作数栈,记录每一个新创建的值

2、variable table 变量表,记录着每一个变量的值

这个两个数据结构随着方法体创建和销毁,而在方法体里定义的变量,比如x,则是一个“引用”,或者说“指针”。

了解了以上,很容易就能理解这一行:

// Stack: 2, Locals: 3

定义Stack长度为2,局部变量表长度为3

Stack为2,很好理解,因为有0和1两个值

局部变量明明只有x和i,为什么会是3呢?其实这里面还隐含了一个this,下面的字节码里体现了这一点:

      Local variable table:
        [pc: 0, pc: 15] local: this index: 0 type: DialogTest
        [pc: 2, pc: 15] local: i index: 1 type: int
        [pc: 7, pc: 15] local: x index: 2 type: int

 

所以,test1方法体的结构大致如下所示:

大学生就业培训,高中生培训,在职人员转行培训,企业团训

 

 

我们来跟着代码一步一步执行。

1、iconst_0:首字母i表示int,const则是表示常量,_0表示值为0。结合起来,就是创建一个int类型的0常量,并且入栈。

如下图所示:

大学生就业培训,高中生培训,在职人员转行培训,企业团训

 

2、istore_1[i]:store相关指令是用于操作变量表的,表示取栈顶指存入变量指定位置,具体来说,即是取栈顶的int值,存入变量表位置为1,i指向的位置

如下图所示:

大学生就业培训,高中生培训,在职人员转行培训,企业团训

 

 

3、iload_1[i]:load相关指令用于操作,表示将变量表里指定的值入栈:

大学生就业培训,高中生培训,在职人员转行培训,企业团训

 

4、iinc 1 1[i]:inc指令代表increase,增加。之后的第一参数1表示变量表位置1,第二个参数1表示数值1。这一句实现了变量的更改:

大学生就业培训,高中生培训,在职人员转行培训,企业团训

 

5、istore_1[x]:应用之前讲解过的store命令的概念,可以知道,这条指令修改了变量表位置2(也就是x)的值:

大学生就业培训,高中生培训,在职人员转行培训,企业团训

 

 如此,就完成了一次i++操作。

 

++i操作的不同之处在于,它调换了步骤3和4。

导致栈顶元素在赋值给x之前,变为了1,如下图所示:

大学生就业培训,高中生培训,在职人员转行培训,企业团训

到这里,相信你已经掌握了最基本的字节码阅读理解。下面引用的链接里是jvm指令集:

 

JVM指令集

 


延伸阅读

告别“老顽固”-Java培训,做最负责任的教育,学习改变命运,软件学习,再就业,大学生如何就业,帮大学生找到好工作,lphotoshop培训,电脑培训,电脑维修培训,移动软件开发培训,网站设计培训,网站建设培训告别“老顽固”