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方法体的结构大致如下所示:

我们来跟着代码一步一步执行。
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指令集:
Java:从面试题“i++和++i哪个效率高?"开始学习java字节码的更多相关文章
- Java并发杂谈(一):volatile的底层原理,从字节码到CPU
volatile的特性 volatile是Java中用于修饰变量的关键字,其主要是保证了该变量的可见性以及顺序性,但是没有保证原子性:其是Java中最为轻量级的同步关键字: 接下来我将会一步步来分析v ...
- Java Record 的一些思考 - 默认方法使用以及基于预编译生成相关字节码的底层实现
快速上手 Record 类 我们先举一个简单例子,声明一个用户 Record. public record User(long id, String name, int age) {} 这样编写代码之 ...
- Java常见面试题总结
一.Java基础 1.String类为什么是final的. 2.HashMap的源码,实现原理,底层结构. 3.说说你知道的几个Java集合类:list.set.queue.map实现类咯... 4. ...
- JAVA 综合面试题
JAVA 综合面试题 2007-08-12 目录 TOC \o "1-3" \h \z \u Java面试题整理 9 Java面向对象 9 1. super()与this()的区别 ...
- Java高级工程师面试题总结及参考答案
一.面试题基础总结 1. JVM结构原理.GC工作机制详解 答:具体参照:JVM结构.GC工作机制详解 ,说到GC,记住两点:1.GC是负责回收所有无任何引用对象的内存空间. 注意:垃圾回收回 ...
- Java工程师面试题集锦
即将踏上找工作的征途,参考网上面试题库准备一波面试题,希望能找到理想中的工作,愿一切顺利. 一.Java基础 1.String类为什么是final的. 2.HashMap的源码,实现原理,底层结构. ...
- 2019年全网最热门的123个Java并发面试题总结
前言 并发编程几乎是所有互联网公司面试必问的问题,并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能.它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰.思维缜密, ...
- 最新Java校招面试题及答案
本文作者在一年之内参加过多场面试,应聘岗位均为 Java 开发方向.在不断的面试中,分类总结了 Java 开发岗位面试中的一些知识点. 主要包括以下几个部分: Java 基础知识点 Java 常见集合 ...
- 《深入理解Java虚拟机》-----第8章 虚拟机字节码执行引擎——Java高级开发必须懂的
概述 执行引擎是Java虚拟机最核心的组成部分之一.“虚拟机”是一个相对于“物理机”的概念 ,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的,而 ...
随机推荐
- ArcGIS三种方式打断相交线------拓扑法
拓扑法:有多个layer图层相交线,选用拓扑法,将多个图层相交线打断. 新建拓扑结构: (1)单击新建"Nfg.gdb"数据库文件: (2)单击新建"XX"集合 ...
- asp.net权限认证:摘要认证(digest authentication)
asp.net权限认证系列 asp.net权限认证:Forms认证 asp.net权限认证:HTTP基本认证(http basic) asp.net权限认证:Windows认证 asp.net权限认证 ...
- (转)java二维数组的深度学习(静态与动态)
转自:http://developer.51cto.com/art/200906/128274.htm,谢谢 初始化: 1.动态初始化:数组定义与为数组分配空间和赋值的操作分开进行:2.静态初始化:在 ...
- ARM处理器:开放者的逆袭
作者:Vamei 出处:http://www.cnblogs.com/vamei 严禁转载. 1981年,英国BBC电视台策划了一系列关于计算机的电视节目.但导演发现一个问题:怎么给没见过电脑的观众画 ...
- Ajax跨域实现淘宝/百度搜索下拉提示效果
最近学到Ajax,觉得自己对与前后端的数据交互有了一个基本的了解.下面是Ajax应用到淘宝/百度的搜索功能的一个简单的小实例,就是输入一个词,下拉框中自动显示匹配的内容:
- ubuntu14.04 + OpenCV2.4.9 配置方法
1. 安装openCV 所需依赖库或软件: sudo apt-get install build-essential cmake libgtk2.0-dev pkg-config python-de ...
- 面向对象重写(override)与重载(overload)区别 (转)
一.重写(override) override是重写(覆盖)了一个方法,以实现不同的功能.一般是用于子类在继承父类时,重写(重新实现)父类中的方法. 重写(覆盖)的规则: 1.重写方法的参数列表必须完 ...
- Asp.Net MVC 之 Autofac 初步使用2 集成mvc 属性注入以及自动注入
首先看下Demo2的结构 然后下面一步步将Autofac集成到mvc中. 首先,定义Model Product.cs public class Product { public int Id ...
- 《Django By Example》第八章 中文 翻译 (个人学习,渣翻)
书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:还有4章!还有4章全书就翻译完成了 ...
- 分页控件AspNetPager学习笔记
1.AspNetPager简介 AspNetPager是一款开源.简单易用.可定制化等等各种优点的Web分页控件. 2.使用方法 1)下载AspNetPager.dll文件(http://www.we ...