原创:微信公众号 码农参上,欢迎分享,转载请保留出处。

前几天,一个小伙伴告诉我,他在面试的时候被面试官问了这么一个问题:

在for循环中,到底应该用 i++ 还是 ++i ?

听到这,我感觉这面试官确实有点不按套路出牌了,放着好好的八股文不问,净整些幺蛾子的东西。在临走的时候,小伙伴问面试官这道题的答案是什么,面试官没有明确告诉答案,只是说让从程序执行的效率角度自己思考一下。

好吧,既然这个问题被抛了出来,那我们就见招拆招,也给以后面试的小伙伴们排一下坑。

思路

前面提到,这个搞事情的面试官说要从执行效率的角度思考,那我们就抛开语义上的区别,从运行结果以外的效率来找找线索。回想一下,我们在以前介绍CAS的文章中提到过,后置自增i++和前置自增++i都不是原子操作,那么实际在执行过程中是什么样的呢?下面,我们从字节码指令的角度,从底层进行一波分析。

i++ 执行过程

先写一段简单的代码,核心功能就只有赋值和自增操作:

public static void main(String[] args) {
int i=3;
int j=i++;
System.out.println(j);
}

下面用javap对字节码文件进行反编译,看一下实际执行的字节码指令:

是不是有点难懂?没关系,接下来我们用图解的形式来直观地看看具体执行的过程,也帮大家解释一下晦涩的字节码指令是如何操作栈帧中的数据结构的,为了简洁起见,在图中只列出栈帧中比较重要的操作数栈局部变量表

上面的代码中除去打印语句,整体可以拆分成两步,我们先看第一步 int i=3 是如何执行的 。

上面两条操作数栈和局部变量表相关的字节码指令还是比较容易理解的,下面再看一下第二步int j=i++的执行过程:

在上图中需要注意的是,iinc能够直接更新局部变量表中的变量值,它不需要把数值压到操作数栈中就能够直接进行操作。在上面的过程中,抛去赋值等其他操作,i++实际执行的字节码指令是:

2: iload_1
3: iinc 1, 1

如果把它翻译成我们能看懂的java代码,可以理解为:

int temp=i;
i=i+1;

也就是说在这个过程中,除了必须的自增操作以外,又引入了一个新的局部变量,接下来我们再看看++i的执行过程。

++i 执行过程

我们对上面的代码做一点小小的改动,仅把i++换成++i,再来分析一下++i的执行过程是怎样的。

public static void main(String[] args) {
int i=3;
int j=++i;
System.out.println(j);
}

同样,用javap反编译字节码文件:

int i=3对应前两行字节码指令,执行过程和前面i++例子中完全相同,可以忽略不计,重点还是通过图解的方式看一下int j=++i对应的字节码指令的执行过程:

抛去赋值操作,++i实际执行过程只有一行字节码指令:

2: iinc    1, 1

转换成能理解的java代码的话,++i实际执行的就在局部变量中执行的:

i=i+1;

这么看来,在使用++i时确实比i++少了一步操作,少引入了一个局部变量,如果在运算结果相同的场景下,使用++i的话的确效率会比i++高那么一点点。

那么回到开头的问题,两种自增方式应用在for循环中执行的时候,那种效率更高呢?刚才得出的结论仍然适用于for循环中吗,别急,让我们接着往下看。

for循环中的自增

下面准备两段包含了for循环的代码,分别使用i++后置自增和++i前置自增:

//i++ 后置自增
public class ForIpp {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
}
}
//++i 前置自增
public class ForPpi {
public static void main(String[] args) {
for (int i = 0; i < 5; ++i) {
System.out.println(i);
}
}
}

老规矩,还是直接反编译后的字节码文件,然后对比一下指令的执行过程:

到这里,有趣的现象出现了,两段程序执行的字节码指令部分居然一模一样。先不考虑为什么会有这种现象,我们还是通过图解来看一下字节码指令的执行过程:

可以清晰的看到,在进行自增时,都是直接执行的iinc,在之前并没有执行iload的过程,也就是说,两段代码执行的都是++i。这一过程的验证其实还有更简单的方法,直接使用idea打开字节码文件,就可以看到最终for循环中使用的相同的前置自增方式。

那么,为什么会出现这种现象呢?归根结底,还是java编译器对于代码的优化,在两种自增方式中,如果没有赋值操作,那么都会被优化成一种方式,就像下面的两个方法的代码:

void ipp(){
int i=3;
i++;
}
void ppi(){
int i=3;
++i;
}

最终执行时的字节码指令都是:

0: iconst_3
1: istore_1
2: iinc 1, 1
5: return

可以看到,在上面的这种特定情况下,代码经过编译器的优化,保持了语义不变,并通过转换语法的形式提高了代码的运行效率。所以再回到我们开头的问题,就可以得出结论,在for循环中,通过jvm进行编译优化后,不论是i++还是++i,最终执行的方式都是++i,因此执行效率是相同的。

所以,以后再碰到这种半吊子的面试官,和你谈for循环中i++++i的效率问题,自信点,直接把答案甩在他的脸上,两种方式效率一样!

本文代码基于Java 1.8.0_261-b12 版本测试

作者简介,码农参上,一个热爱分享的公众号,有趣、深入、直接,与你聊聊技术。个人微信DrHydra9,欢迎添加好友,进一步交流。

面试官又整新活,居然问我for循环用i++和++i哪个效率高?的更多相关文章

  1. 面经手册 · 第12篇《面试官,ThreadLocal 你要这么问,我就挂了!》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 说到底,你真的会造火箭吗? 常说面试造火箭,入职拧螺丝.但你真的有造火箭的本事吗,大 ...

  2. 面试官写了个双冒号: : 问我这是什么语法?Java中有这玩意?

    一:简洁 方法引用分为三种,方法引用通过一对双冒号:: 来表示,方法引用是一种函数式接口的另一种书写方式 静态方法引用,通过类名::静态方法名, 如 Integer::parseInt 实例方法引用, ...

  3. 阿里二面,面试官居然把 TCP 三次握手问的这么细致

    TCP 的三次握手和四次挥手,可以说是老生常谈的经典问题了,通常也作为各大公司常见的面试考题,具有一定的水平区分度.看似是简单的面试问题,如果你的回答不符合面试官期待的水准,有可能就直接凉凉了. 本文 ...

  4. 面试官问我MySQL调优,我真的是

    面试官:要不你来讲讲你们对MySQL是怎么调优的? 候选者:哇,这命题很大阿...我认为,对于开发者而言,对MySQL的调优重点一般是在「开发规范」.「数据库索引」又或者说解决线上慢查询上. 候选者: ...

  5. 8年经验面试官详解 Java 面试秘诀

      作者 | 胡书敏 责编 | 刘静 出品 | CSDN(ID:CSDNnews) 本人目前在一家知名外企担任架构师,而且最近八年来,在多家外企和互联网公司担任Java技术面试官,前后累计面试了有两三 ...

  6. 如何准备Java面试?如何把面试官的提问引导到自己准备好的范围内?

    Java能力和面试能力,这是两个方面的技能,可以这样说,如果不准备,一些大神或许也能通过面试,但能力和工资有可能被低估.再仔细分析下原因,面试中问的问题,虽然在职位介绍里已经给出了范围,但针对每个点, ...

  7. 阿里 IOS 面试官教你在面试中脱颖而出

    前言: 知己知彼.百战不殆,面试也是如此. 只有充分了解面试官的思路,才能更好地在面试中充分展现自己. 今天,阿里高级技术专家将分享自己作为面试官的心得与体会.如果你是面试者,可以借此为镜,对照发现自 ...

  8. 作为Java技术面试官,我如何深挖候选人的技能

    作为Java资深技术面试官,首先我感觉有必要讲解"面试官深挖问题"的动机,在了解动机的前提下,大家才能更好地准备面试.面试官为什么要在一个点上深挖?两大目的.   1 首先是通过深 ...

  9. 原创 | 我被面试官给虐懵了,竟然是因为我不懂Spring中的@Configuration

    GitHub 3.7k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 3.7k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 3.7k Star 的 ...

随机推荐

  1. PTA习题6-8 统计一行文本的单词个数 (15分)

    参考<c和指针>里面运用strtok函数打印空白标记符(如\n,\t)的程序改写而成的代码 在之前我自己写了一个60行的链表版本的统计程序 相比之下这个strtok函数的程序要简洁明了的多 ...

  2. Mybatis初始化机制

    对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章将通过以下几点详细介绍MyBatis的初始化过程. 1.MyBatis的初始化做了什么 2. MyBatis基于XML配置 ...

  3. Golang通脉之数组

    数组是同一种数据类型元素的集合.数组在内存中都是连续存放的. 在Go语言中,数组从声明时就确定,使用时可以修改数组成员,但是数组大小不可变化. 基本语法: // 定义一个长度为3元素类型为int的数组 ...

  4. DOS命令和快捷键

  5. 在Windows上使用Docker 创建MongoDB 副本集的极简方法(翻译)

    这篇博客介绍下在Windows上使用Docker 创建MongoDB 三节点副本集的最简单的方法.以下命令需要Docker for Windows并使用Linux 容器. 1: 为每个节点创建数据卷 ...

  6. [no_code][Beta]设计和计划

    2020春季计算机学院软件工程(罗杰 任健) 2020春季计算机学院软件工程(罗杰 任健) 作业要求 Beta设计和计划 我们在这个课程的目标是 远程协同工作,采用最新技术开发软件 这个作业在哪个具体 ...

  7. 5.31日 Scrum Metting

    日期:2021年5月31日 会议主要内容概述:讨论草稿箱前后端接口,讨论账单页面设计. 一.进度情况 组员 负责 两日内已完成的工作 后两日计划完成的工作 工作中遇到的困难 徐宇龙 后端 文件导入功能 ...

  8. Noip模拟69 2021.10.5

    考场拼命$yy$高精度结果没学好$for$循环痛失$50pts$,当场枯死 以后一定打对拍,要不考后会... T1 石子游戏 首先要知道典型的$NIM$博弈,就是说如果所有堆石子个数的异或和为$0$则 ...

  9. Java并发:ReadWriteLock 读写锁

    读写锁在同一时刻可以允许多个线程访问,但是在写线程访问,所有的读线程和其他写线程均被阻塞. 读写锁不像 ReentrantLock 那些排它锁只允许在同一时刻只允许一个线程进行访问,读写锁可以允许多个 ...

  10. 到底能不能用 join

    互联网上一直流传着各大公司的 MySQL 军规,其中关于 join 的描述,有些公司不推荐使用 join,而有些公司则规定有条件的使用 join, 它们都是教条式的规定,也没有详细说其中的原因,这就很 ...