今天看到一道面试题,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方法体的结构大致如下所示:

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

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:从面试题“i++和++i哪个效率高?"开始学习java字节码的更多相关文章

  1. Java并发杂谈(一):volatile的底层原理,从字节码到CPU

    volatile的特性 volatile是Java中用于修饰变量的关键字,其主要是保证了该变量的可见性以及顺序性,但是没有保证原子性:其是Java中最为轻量级的同步关键字: 接下来我将会一步步来分析v ...

  2. Java Record 的一些思考 - 默认方法使用以及基于预编译生成相关字节码的底层实现

    快速上手 Record 类 我们先举一个简单例子,声明一个用户 Record. public record User(long id, String name, int age) {} 这样编写代码之 ...

  3. Java常见面试题总结

    一.Java基础 1.String类为什么是final的. 2.HashMap的源码,实现原理,底层结构. 3.说说你知道的几个Java集合类:list.set.queue.map实现类咯... 4. ...

  4. JAVA 综合面试题

    JAVA 综合面试题 2007-08-12 目录 TOC \o "1-3" \h \z \u Java面试题整理 9 Java面向对象 9 1. super()与this()的区别 ...

  5. Java高级工程师面试题总结及参考答案

    一.面试题基础总结 1. JVM结构原理.GC工作机制详解 答:具体参照:JVM结构.GC工作机制详解     ,说到GC,记住两点:1.GC是负责回收所有无任何引用对象的内存空间. 注意:垃圾回收回 ...

  6. Java工程师面试题集锦

    即将踏上找工作的征途,参考网上面试题库准备一波面试题,希望能找到理想中的工作,愿一切顺利. 一.Java基础 1.String类为什么是final的. 2.HashMap的源码,实现原理,底层结构. ...

  7. 2019年全网最热门的123个Java并发面试题总结

    前言 并发编程几乎是所有互联网公司面试必问的问题,并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能.它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰.思维缜密, ...

  8. 最新Java校招面试题及答案

    本文作者在一年之内参加过多场面试,应聘岗位均为 Java 开发方向.在不断的面试中,分类总结了 Java 开发岗位面试中的一些知识点. 主要包括以下几个部分: Java 基础知识点 Java 常见集合 ...

  9. 《深入理解Java虚拟机》-----第8章 虚拟机字节码执行引擎——Java高级开发必须懂的

    概述 执行引擎是Java虚拟机最核心的组成部分之一.“虚拟机”是一个相对于“物理机”的概念 ,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的,而 ...

随机推荐

  1. Hibernate调用带有输入参数,输出参数为cursor的存储过程

    一.Oracle创建表及存储过程 1.创建表T_MONITOR_DEVICE 创建后的表结构 2.创建存储过程 create or replace procedure ProcTestNew(v_mo ...

  2. Swift2.2 看完这篇博客 你不想懂也会懂得----二叉树

    一:初衷 我自己也好奇,为什么莫名其妙的想起写这个,其实数据里面包含的结构和逻辑我自己觉得才是最原始经典的,最近也在学swift,就向着利用swift整理一些二叉树.自己刚开始的时候也是用OC看着别的 ...

  3. (转)JAVA的整型与字符串相互转换

    JAVA的整型与字符串相互转换1如何将字串 String 转换成整数 int? A. 有两个方法: 1). int i = Integer.parseInt([String]); 或         ...

  4. Sqlserver 链接服务器和同义词

    在数据库的日常维护中,经常会遇到跨服务器的数据传输. 例如A服务器上的数据每天要从B服务器上去获取数据,然后插入到自己的服务器上.这种情况就要用到链接服务器了. 接下来,我就把我本机当作服务器A,17 ...

  5. 2017-2-21 C#基础 if条件语句,作用域

    今天学了if 条件语句和作用域.作用域可以用一句话来概括:儿子可以用爹的所有东西,爹不可以用儿子的任何东西.If条件语句我用几个练习题来解释. 1."请输入年份:" 判断是否是闰年 ...

  6. Winfrom 程序打包及安装

    前言 近期被公司外派到驻空调厂的项目组,支援一个TCP相关的程序对接.主要是做智能门禁系统,然后主要是统计出实时的进出人数. 我这边能作为服务端,门禁设备作为客户端,整个流程并不算复杂,根据协议来写, ...

  7. 利用Eclipse的JPA自动生成注解实体

    新公司用的SSH(springmvc)框架,看代码的时候,发现没有hbm.xml文件,全部使用的注解形式.在一次闲聊的时候问同事,这么多entity  写起来不麻烦么.同事说根据数据库自动生成的.于是 ...

  8. 每天一个linux命令(47)--scp命令

    scp是secure  copy 的简写,用于在Linux 下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝而不能跨服务器,而且scp传输是加密的.可能会稍微影响一下速度.当 ...

  9. 自定义view(一)

    为什么标题会是自定义view(一)呢?因为自定义view其实内容很多,变化也很多,所以我会慢慢更新博客,争取多写的有关的东西,同时,如果我以后学到了新的有关于自定义view的东西,我也会及时写出来. ...

  10. 【2017-02-23】switch...case...和for循环

    1.代码简化折叠: #region 标题 ... ... #endregion 一.switch...case... 1.格式 switch(变量){ case 值:代码段;break; case 值 ...