前言:众所周知,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. GetX 关于报错 Null check operator used on a null value的解决

    import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'logic.dart'; class Ge ...

  2. 2023icpc大学生程序设计竞赛-zzh

    这次比赛是第一次去外地打比赛,感觉挺好的.洛阳师范绿化感觉比我们学校好很多,校园看起来也挺大的.群里说牛肉汤是洛阳特色,比赛当天上午特地跑了两个餐厅,找到了一家牛肉汤,吃起来挺一般的,不过这家的酱香饼 ...

  3. 【MAUI Blazor踩坑日记】6.mac标题栏颜色修改

    MAUI中mac的标题栏颜色默认是灰白色的,有一点丑 如果我们想要自定义颜色,并且在运行时也能更改颜色,该怎么办呢 万幸从一个GitHub库中借鉴到了办法 https://github.com/Ben ...

  4. QMainWindow类中比较重要的方法

    方法和描述 addToolBar():添加工具栏 centralWidget():返回窗口中心的一个空间,未设置时返回NULL menuBar(): 返回主窗口的菜单栏 setCentralWidge ...

  5. js中调用函数中的变量

    (function f1() { var num = 10; window.num = num;})(); console.log(num);

  6. ROC 曲线与 PR 曲线

    ROC 曲线与 PR 曲线 ROC 曲线与 PR 曲线 ROC 曲线和 PR 曲线是评估机器学习算法性能的两条重要曲线,两者概念比较容易混淆,但是两者的使用场景是不同的.本文主要讲述两种曲线的含义以及 ...

  7. 创建本地yum仓库

    创建本地yum仓库 1,将镜像挂载到/mnt 如果失败打开虚拟机把设备状态的两个选项打勾 2,切换到客户端的指定目录 3,创建文件夹bak存放网络yum创库配置文件 4,将网络源移动到bak减少干扰 ...

  8. [golang]使用mTLS双向加密认证http通信

    前言 假设一个场景,服务端部署在内网,客户端需要通过暴露在公网的nginx与服务端进行通信.为了避免在公网进行 http 明文通信造成的信息泄露,nginx与客户端之间的通信应当使用 https 协议 ...

  9. 如何爆破js加密后的密码?

    如何爆破js加密后的密码? 1.首先burp中安装插件: https://github.com/whwlsfb/BurpCrypto 安装插件完毕后,分析进行js加密的算法. 2.分析加密过程: 找到 ...

  10. 通过替换dll实现后门功能的恶意代码

    通过替换Kernel32.dll来实现的后门功能的恶意代码. 该恶意代码存在一个exe可执行文件和一个dll动态链接库,需要分别进行分析 一.待解决问题 这个恶意代码执行了什么功能? 通过什么方式实现 ...