前言:众所周知,i++++i的区别是:i++先将i的值赋值给变量,再将i的值自增1;而++i则是先将i的值自增1,再将结果赋值给变量。因此,二者最终都给i自增了1,只是方式不同而已。

当然,如果在面试过程中面试官问你这个问题,只回答出上述内容,只能说明你对这方面的知识了解的还是太浅显。那么i++++i到底有什么不同之处呢?

一、局部变量表与操作数栈简介

《深入理解Java虚拟机》第八章对栈帧结构有如下描述Java虚拟机以方法作为最基本的执行单元,“栈帧”(Stack Frame)则是用于支持虚拟机进行方法调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈的栈元素。

在一个活动线程中,可能会执行多个方法,因此会存在多个栈帧,和“栈”(先进后出)一样,处于栈顶的栈帧才是真正运行的,处于栈顶的栈帧称作“当前栈帧”(Current Stack Frame),这个栈帧所属的方法称作“当前方法”(Current Method)。

在执行main方法时,main方法所属的线程主线程,假设在主线程中调用了一个method1()方法,在method1()内部调用了method2()方法,在method2()方法执行两个整数运算,示例如下:

/**
* 方法调用
*
* @author iCode504
* @date 2023-10-23 22:05
*/
public class StackFrameDemo1 {
public static void main(String[] args) {
System.out.println("main开始执行");
method1();
System.out.println("main执行完成");
} private static void method1() {
System.out.println("method1开始执行");
int result = method2();
System.out.println("result = " + result);
System.out.println("method1执行结束");
} private static int method2() {
int var1 = 10;
int var2 = 20;
return var1 + var2;
}
}

运行结果:

由代码我们可以看出,main方法最先执行一个输出,然后进入method1执行第一个输出,再完整执行method2method2执行完成以后,再执行method1,最后执行main方法,由于这段代码中只涉及一个主线程,并且最先完整执行方法的是method2,因此method2对应的栈帧就是当前栈帧,main方法最后执行完毕,因此main方法对应的栈帧在method2method1之下。以下是这段代码对应的栈帧概念图:

在每一个栈帧中存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息

1.1 局部变量表

局部变量表(Local variable Table)是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。

局部变量表的容量是以变量槽(Variable Slot)为最小单位,每个变量槽能存储基本数据类型和引用数据类型的数据。为了尽可能节省栈帧消耗的内存空间,局部变量表中的变量槽是可以重用的。

JVM使用索引定位的方式使用索引变量表,索引值的范围是从0开始到局部变量表最大变量槽的数量(类似数组结构)。

当一个方法被调用的时候,JVM会使用局部变量表来完成参数值到参数变量列表的传递,即实参到形参的传递。

1.2 操作数栈

操作数栈(Operand Stack)也称作操作数栈,它是一个栈结构(后进先出,例如手枪的弹夹,先打出去的子弹是最顶上的子弹)。

在方法开始执行的时候,这个方法对应的操作数栈是空的,在方法执行过程中,会有各种字节码指令向操作数栈中写入或读取内容,即出栈和入栈操作,例如:两数相加运算时,就需要将两个数压入栈顶后调用运算指令。

操作数栈中的元素的数据类型必须和字节码指令序列严格匹配,在编译程序代码的时候编译器必须要严格保证这一点,在类的校验阶段的数据流分析时候还需要再次校验。例如:执行加法iaddiint类型,add是两个数相加)命令时,就需要保证两个操作数必须是int类型,不能出现其他类型相加的情况。

二、字节码分析(图解)

我们可以从字节码的角度进一步对i++++i的执行过程做进一步的分析。以下面代码为例:

/**
* i++和++i的深入分析
*
* @author iCode504
* @date 2023-10-17 5:58
*/
public class IncrementAndDecrementOperators2 {
public static void main(String[] args) {
int intValue1 = 2;
int intValue2 = 2;
int result1 = intValue1++;
int result2 = ++intValue2;
System.out.println("result1 = " + result1);
System.out.println("result2 = " + result2);
}
}

我们需要查看编译后的字节码文件,字节码文件不能直接使用记事本打开,但是我们可以使用javap -verbose 文件名.class命令,以IncrementAndDecrementOperators2.class为例:

javap -verbose IncrementAndDecrementOperators2.class

此时就会打开所有的字节码文件,我们只需要关注main方法内的执行过程即可:

首先来解释一下这四行代码的含义:

0: iconst_2
1: istore_1
2: iconst_2
3: istore_2
  • iconst_2一共有两部分组成,i指的是int类型(源代码中我们定义的确实是int类型),const代表常量(数字2是整型常量),iconst_2的含义是将2入操作数栈。
  • istore_1中的store代表的是存储,istore_1的含义是将操作数栈中的数值2出栈,存入到局部变量表1的位置。同理,i_store2表示将操作数栈中的数值2出栈,存储到局部变量表2的位置。

以下是前面四行代码存储过程图(存储过程全部流程图点击此链接下载:点我下载):

此时我们继续观察4-8行代码:

4: iload_1
5: iinc 1, 1
8: istore_3
  • iload_1的作用是将局部变量表1号位置存储的值移动到操作数栈的栈顶。
  • 第5行的iinc有两个参数,第一个参数1是局部变量表的位置,另一个参数1的含义是在该位置存储一个1,如果这个位置存在值,那么这个值的结果是已存在值 + 参数值
  • istore_3将操作数栈中的数移动到局部变量表的3号位置。

以下是这三行代码的示意图:

9-12行的字节码的作用原理和4-8行的作用原理基本相同:

9: iinc			2, 1
12: iload_2
13: istore 4

istore 4的作用是将操作数栈中的值存储到局部变量表4号位置。

以下是这三行代码的示意图:

接下来15-30行是和系统输出有关的。其中第30行iload_3在局部变量表中(这个值为2)值移动到操作数栈顶供系统输出,事实上iload_3的值正好对应源代码中变量result1的值。也就是说,result1输出结果就是iload_3的数值2。

同理,iload 4就是第二个要输出的值,在局部变量表中第4个位置存储的值正好是3,而输出的变量名是result2,因此result2的输出结果是3。

三、i++++i性能分析

i++++i主要用在普通for循环上,那么我们就将二者用在for循环上,循环相同的次数,从字节码的角度进行分析。

以下是使用i++++i的两个for循环文件:

/**
* i++在for循环的使用
*
* @author ZhaoCong
* @date 2023-10-21 16:14:33
*/
public class LoopTest1 {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) { }
}
}
/**
* ++i在for循环的使用
*
* @author ZhaoCong
* @date 2023-10-21 16:15:17
*/
public class LoopTest2 {
public static void main(String[] args) {
for (int i = 0; i < 100; ++i) { }
}
}

执行编译命令以后,我们来查看两个文件的字节码:

仔细观察这两个字节码文件内容,我们发现在两个文件main方法的字节码内容完全相同。由此可见,两种方式执行for循环的效率是相同的。

入门篇-其之六-附录一-以Java字节码的角度分析i++和++i的更多相关文章

  1. JVM Java字节码的角度分析switch的实现

    目录 Java字节码的角度分析switch的实现 引子 前置知识 一个妥协而又枯燥的方案 switch的实现 回顾历史 字节码分析 其他实现方式? Java字节码的角度分析switch的实现 作者 k ...

  2. i = i++ 在java字节码层面的分析

    有这么一段代码: package zl.test; public class PcodeTest { /** * @param args */ public static void main(Stri ...

  3. Java字节码—ASM

    前言 ASM 是什么 官方介绍:ASM is an all purpose Java bytecode manipulation and analysis framework. It can be u ...

  4. Java字节码分析

    目录 Java字节码分析 查看字节码详细内容 javap 实例分析 Java字节码分析 对于源码的效率,但从源码来看有时无法分析出准确的结果,因为不同的编译器版本可能会将相同的源码编译成不同的字节码, ...

  5. 通过Java字节码发现有趣的内幕之String篇(上)(转)

    原文出处: jaffa 很多时候我们在编写Java代码时,判断和猜测代码问题时主要是通过运行结果来得到答案,本博文主要是想通过Java字节码的方式来进一步求证我们已知的东西.这里没有对Java字节码知 ...

  6. 从 HelloWorld 看 Java 字节码文件结构

    很多时候,我们都是从代码层面去学习如何编程,却很少去看看一个个 Java 代码背后到底是什么.今天就让我们从一个最简单的 Hello World 开始看一看 Java 的类文件结构. 在开始之前,我们 ...

  7. 在Eclipse里查看Java字节码

    要理解 Java 字节码,比较推荐的方法是自己尝试编写源码对照字节码学习.其中阅读 Java 字节码的工具必不可少.虽然javap可以以可读的形式展示出.class 文件中字节码,但每次改动源码都需调 ...

  8. 【转】在Eclipse里查看Java字节码

    要理解 Java 字节码,比较推荐的方法是自己尝试编写源码对照字节码学习.其中阅读 Java 字节码的工具必不可少.虽然javap可以以可读的形式展示出.class 文件中字节码,但每次改动源码都需调 ...

  9. Java字节码(.class文件)格式详解(一)

    原文链接:http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html 小介:去年在读<深入解析JVM>的时候写的,记得当时还 ...

  10. 空手套白狼,硬阅java字节码class文件

    如下,是一些java字节码也就是原始的class文件,当应用部署到线上之后,我们能够看到的也就是这样的字样了.那么怎样解呢?就让我们一起,来解读解读字节码吧! Offset A B C D E F C ...

随机推荐

  1. 选择排序与冒泡排序(c语言+Java语言)

    选择排序 O(n2) 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾. 重复第二步,直到所有元素均排序完毕. 例 ...

  2. 创建属于自己的github、使用git提交、更新代码至github、写好readme

    1. 在github上创建一个Repository 点击github网站,你可以用你的邮箱先注册一个账号. 点击New,转到创建一个repository的界面,如下图所示,你可以填写你的Reposit ...

  3. Django+DRF+Vue 网页开发环境安装(windows/Linux)

    博客地址:https://www.cnblogs.com/zylyehuo/ 总览 一.安装 Django pip install django==3.2 二.安装 MySQL 驱动程序 pip in ...

  4. 详解TCP网络协议栈的工作原理

    本文分享自华为云社区<网络通信的神奇之旅:解密Linux TCP网络协议栈的工作原理>,作者: Lion Long . 一.TCP网络开发API TCP,全称传输控制协议(Transmis ...

  5. 最全面的JAVA多线程知识总结

    ​ 背景: 2023年经营惨淡,经历了裁员就业跳槽再就业,在找工作过程中对于知识的梳理和总结,本文总结JAVA多线程. 应用场景: 需要同时执行多个任务或处理大量并发请求时, 目前常用的场景有: We ...

  6. CF1855B Longest Divisors Interval 题解

    题意: 给定一个数 \(n\),求一个连续区间 \([l,r]\) 使得 \(n\) 是区间内每个数的倍数,最大化这个区间的长度(多组数据). 思路: 逆向思考一波,( 如果一个数 \(x\) 不是 ...

  7. JS中文件相关的知识(一):MIME类型

    不知道有没有同学和我一样,写代码时一遇到文件操作就犯怵,必须要先去把知识补一遍再说:对于Content-Type.responseType.ArrayBuffer.buffer.blob.file等这 ...

  8. C# OpenCVSharp图像入门_给绿幕图片视频加背景

    OpenCV的全称是Open Source Computer Vision Library,是一个跨平台的计算机视觉库.OpenCV是由英特尔公司发起并参与开发,以BSD许可证授权发行,可以在商业和研 ...

  9. 产品代码都给你看了,可别再说不会DDD(二):DDD概念大白话

    这是一个讲解DDD落地的文章系列,作者是<实现领域驱动设计>的译者滕云.本文章系列以一个真实的并已成功上线的软件项目--码如云(https://www.mryqr.com)为例,系统性地讲 ...

  10. Deno 中使用 @typescript/vfs 生成 DTS 文件

    背景 前段时间开源的 STC 工具,这是一个将 OpenApi 规范的 Swagger/Apifox 文档转换成代码的工具.可以在上一篇(<OpenApi(Swagger)快速转换成 TypeS ...