面试官又整新活,居然问我for循环用i++和++i哪个效率高?

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

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

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

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

思路

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

i++ 执行过程

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

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

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

063d259fba6e91816842f3443ce52797.png

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

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

8b4aac91ee1377610cc65a36d5b677d4.png

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

073ef8ff02bef9871c4cb3dcc2333f2a.png

在上图中需要注意的是,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反编译字节码文件:

2b0261d3582032fdd1d0ac14ed1bbb31.png

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

4f68a34aa776b0aef3b524776a9c7334.png

抛去赋值操作,++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);}}
}

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

443c4cdcc13c67f87b4502077830fb19.png

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

639102dee269d6b4c985fe1621f599af.png

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

a89304aae3f4c9d6120d2eb56b6bc23d.png

那么,为什么会出现这种现象呢?归根结底,还是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 版本测试

911c1884731350dca9d5c67f48683fd4.gif

往期推荐

990cb4ffaa995329f3aced12970fb133.png

面试官:HashMap有几种遍历方法?推荐使用哪种?


4098209bc68ba0fff47f79b2a11d6338.png

面试官:重写 equals 时为什么一定要重写 hashCode?


df689dc12605de6c8632b09157a4e27a.png

List 去重的 6 种方法,这个方法最完美!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544421.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UVa 988 - Many Paths, One Destination

称号&#xff1a;生命是非常多的选择。现在给你一些选择&#xff08;0~n-1&#xff09;&#xff0c;和其他选项后&#xff0c;分支数每一次选择&#xff0c;选择共求。 分析&#xff1a;dp&#xff0c;图论。假设一个状态也许是选择的数量0一个是&#xff0c;代表死亡&#xff…

Java PrintWriter close()方法与示例

PrintWriter类close()方法 (PrintWriter Class close() method) close() method is available in java.io package. close()方法在java.io包中可用。 close() method is used to close this stream and free all system resources linked with the stream. close()方法用于关闭…

pipedreader_Java PipedReader ready()方法与示例

pipedreaderPipedReader类ready()方法 (PipedReader Class ready() method) ready() method is available in java.io package. ready()方法在java.io包中可用。 ready() method is used to check whether this PipedReader stream is ready to be read or not. ready()方法用…

面试官:如何实现 List 集合去重?

作者 | 磊哥来源 | Java面试真题解析&#xff08;ID&#xff1a;aimianshi666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;本文已收录《Java常见面试题》系列&#xff0c;开源地址&#xff1a;https://gitee.com/mydb/interviewList 去重指的…

Windows重装Anaconda3失败解决方案【重装失败10来次首次成功的案例!】

文章目录0.环境1.原因2.解决方案0.环境 Win10 Anaconda3 2018版 python 3.7.1 注意&#xff01;此种情况只会在windows上发生&#xff0c;因为在linux上你只需要删除anaconda3整个文件夹&#xff0c;重新安装一定会成功&#xff01; 1.原因 Anaconda肯定是没有成功安装的&am…

java写入文件的几种方法分享

转自&#xff1a;http://www.jb51.net/article/47062.htm 一&#xff0c;FileWritter写入文件 FileWritter, 字符流写入字符到文件。默认情况下&#xff0c;它会使用新的内容取代所有现有的内容&#xff0c;然而&#xff0c;当指定一个true &#xff08;布尔&#xff09;值作为…

python读取pcd点云/转numpy(python2+python3,非ROS环境)

0.引言 \qquadROS的PCL库支持python读取点云&#xff0c;ROS1关联的是python2&#xff08;2.7&#xff09;&#xff0c;ROS2关联的是python3&#xff08;>3.5&#xff09;&#xff0c;但这对于windows的用户和没装ROS的ubuntu用户似乎不够友好。下面就介绍两种不需要ros的方…

Java中List排序的3种方法!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;在某些特殊的场景下&#xff0c;我们需要在 Java 程序中对 List 集合进行排序操作。比如从第三方接口中获取所有用户的列表&…

setdefault_Java语言环境setDefault()方法及示例

setdefault语言环境类setDefault()方法 (Locale Class setDefault() method) setDefault() method is available in java.util package. setDefault()方法在java.util包中可用。 setDefault() method is used to assign the default locale for this Locale instance of the JV…

Spring 事务失效的 8 种场景!

在日常工作中&#xff0c;如果对Spring的事务管理功能使用不当&#xff0c;则会造成Spring事务不生效的问题。而针对Spring事务不生效的问题&#xff0c;也是在跳槽面试中被问的比较频繁的一个问题。点击上方卡片关注我今天&#xff0c;我们就一起梳理下有哪些场景会导致Spring…

xcode6 AsynchronousTesting 异步任务测试

xcode集成了非常方便的测试框架&#xff0c;XCTest 在xcode6之后&#xff0c;提供了 <XCTest/XCTestCaseAsynchronousTesting.h> 利用此我们可以直接在XCTest里面测试一些异步的任务&#xff0c;比如异步网络请求 如下示例 - (void)testExample {XCTestExpectation *exce…

vscode无法识别constexpr

问题 vscode 无法识别constexpr&#xff08;常指针类型&#xff09; 方法 打开工程路径下的.vscode文件夹&#xff08;一般是自动隐藏的&#xff0c;CtrlH显示隐藏&#xff09;设置c_cpp_properties.json文件如下&#xff1a; {"configurations": [{"name…

三流Java搞技术,二流Java搞框架,一流Java…

如何反驳“99&#xff05; 的 Java 程序员都是 Spring 程序员”这句话&#xff1f;答案是不能。互联网发展至今&#xff0c;站在巨人肩膀上编程像一日三餐一样寻常。Spring Boot 的确凭一己之力拉低了 Java 开发的门槛&#xff0c;可普通开发与高开之间&#xff0c;真就因为一个…

java 方法 示例_Java语言环境getVariant()方法与示例

java 方法 示例区域设置类getVariant()方法 (Locale Class getVariant() method) getVariant() method is available in java.util package. getVariant()方法在java.util包中可用。 getVariant() method is used to get the variant code for this Locale. getVariant()方法用…

2.7-源码编译安装

网上下载源码包 wget http://网址 如果没有wget yum install -y wget建议下载下来的源码包&#xff0c;统一放到/usr/local/scr/下&#xff0c;方便维护管理养成查看INSTALL和README文档的习惯&#xff0c;内有软件安装方法和详细信息。1. ./configure --prefix/usr/l…

【Ubuntu】vscode配置PCL库/vscode无法导入PCL库

问题 PCL库是ROS框架自带的点云处理库&#xff0c;可以通过find_package(PCL REQUIRED)在CMakeLists.txt中导入&#xff0c;但是vscode却无法识别&#xff0c;出现问题如下&#xff1a; 注意&#xff0c;本文解决方案仅限Ubuntu&#xff01; 解决方案 打开工程路径下的.vsc…

面试官:HashSet是如何保证元素不重复的?

作者 | 磊哥来源 | Java面试真题解析&#xff08;ID&#xff1a;aimianshi666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;本文已收录《Java常见面试题》系列&#xff0c;开源地址&#xff1a;https://gitee.com/mydb/interviewHashSet 实现…

java bitset_Java BitSet nextSetBit()方法与示例

java bitsetBitSet类nextSetBit()方法 (BitSet Class nextSetBit() method) nextSetBit() method is available in java.util package. nextSetBit()方法在java.util包中可用。 nextSetBit() method is used to retrieve the index of the first bit that is set to true that …

STM32串口寄存器操作(转)

源&#xff1a;STM32串口寄存器操作 //USART.C/*********************************************************************************************************/ /* USART 收发 */ /* 陈鹏 20110611*/#include "SYSTEM.H" #include "GPIO_INIT.H" #inclu…

【Ubuntu】Ubuntu 20.04无法识别网口/以太网/有线网卡

这里写自定义目录标题0.症状1.查看网卡类型2.下载网卡驱动3.安装网卡驱动0.症状 \qquad表现为插入以太网网口后右上角没有显示网络&#xff0c;即没有下图的音量左侧的标志 打开设置的【网络】选项没有以太网接入&#xff0c;然而以太网口信号灯仍然正常闪烁。这种情况基本可以…