Java:从面试题“i++和++i哪个效率高?"开始学习java字节码

今天看到一道面试题,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条指令。

// 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

  // Method descriptor #6 ()V
  // Stack: 2, Locals: 3
  public void test2();
     0  iconst_0
     1  istore_1 [i]
     2  iinc 1 1 [i]
     5  iload_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: 9]
        [pc: 2, line: 10]
        [pc: 7, line: 11]
        [pc: 14, line: 12]
      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

  // Method descriptor #6 ()V
  // Stack: 2, Locals: 3
  public void test3();
     0  iconst_0
     1  istore_1 [i]
     2  iinc 1 1 [i]
     5  iload_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: 15]
        [pc: 2, line: 16]
        [pc: 5, line: 17]
        [pc: 7, line: 18]
        [pc: 14, line: 19]
      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

  // Method descriptor #6 ()V
  // Stack: 2, Locals: 3
  public void test4();
     0  iconst_0
     1  istore_1 [i]
     2  iinc 1 1 [i]
     5  iload_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: 22]
        [pc: 2, line: 23]
        [pc: 5, line: 24]
        [pc: 7, line: 25]
        [pc: 14, line: 26]
      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

核心的执行过程同为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方法体的结构大致如下所示:

Java:从面试题“i++和++i哪个效率高?"开始学习java字节码

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

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

如下图所示:

Java:从面试题“i++和++i哪个效率高?"开始学习java字节码

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

如下图所示:

Java:从面试题“i++和++i哪个效率高?"开始学习java字节码

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

Java:从面试题“i++和++i哪个效率高?"开始学习java字节码

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

Java:从面试题“i++和++i哪个效率高?"开始学习java字节码

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

Java:从面试题“i++和++i哪个效率高?"开始学习java字节码

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

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

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

Java:从面试题“i++和++i哪个效率高?"开始学习java字节码

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

JVM指令集

 
上一篇:最简单的基于FFMPEG的视频编码器(YUV编码为H.264)


下一篇:I/O操作技术