foreach底层机制
简单例子
直接了解foreach底层有些困难,我们需要从更简单的例子着手.下面上一个简单例子:
public class Simple { public static void main(String[] args) {
int i = ;
System.out.println(i);
}
}
找到其字节码文件所在目录并在目录下打开终端(Windows系统是在目录下shift+鼠标右键选择在此打开powershell窗口)
输入指令:javac -Simple.class >SimpleRunc
目录中得到SimpleRunc文件,打开它,会看到如下代码(里面有我的注释):
Compiled from "Simple.java"
public class cn._012thDay._foreach.Simple {
public cn._012thDay._foreach.Simple();
Code:
: aload_0 将第一个引用型本地变量推送至栈顶;
: invokespecial # // Method java/lang/Object."<init>":()V
调用超类构造方法;
: return public static void main(java.lang.String[]);
Code:
: iconst_5 将int型5推送至栈顶;
: istore_1 将栈顶int型数据存入第二个本地变量;
此处推测:第一个本地变量是超类; : getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
获取本地静态域并将其压入栈顶;即获取静态属性压入栈顶
: iload_1 将第二个int型本地变量推送至栈顶;
: invokevirtual # // Method java/io/PrintStream.println:(I)V
调用实例方法;
获取类属性,加载入栈,打印栈顶,即5 : return
}
如果不懂指令意思,可查询JVM指令表.这里我说明一下步骤:
第一步:加载超类Object类
第二步:将int类型的5压入栈顶,然后将5存入本地变量1
第三部:获取静态属性
第四步:加载本地变量1,即将5推送至栈顶
第五步:打印
for循环
public class ForSimple { public static void main(String[] args) {
for (int i = ; i > ; i-=) {
System.out.println(i);
}
}
}
其实这个例子foreach就做不了,因为foreach遍历必须要有一个数组.
javap -c:
Compiled from "ForSimple.java"
public class cn._012thDay._foreach.ForSimple {
public cn._012thDay._foreach.ForSimple();
Code:
: aload_0
: invokespecial # // Method java/lang/Object."<init>":()V
: return public static void main(java.lang.String[]);
Code:
: iconst_5
: istore_1
: goto
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: iload_1
: invokevirtual # // Method java/io/PrintStream.println:(I)V
: iinc , -
: iload_1
: ifgt
: return
}
main方法第2行goto 15代表无条件跳转至第15行加载变量1,值是5
16行:ifgt 5 如果int型大于零则回到第5行,5>0
5~9行:打印,5
12行:变量1自增-2,即5变为3
15行:加载变量1,值是3
16行:ifgt 5 如果int型大于零则回到第5行,3>0
5~9行:打印,3
12行:变量1自增-2,即3变为1
15行:加载变量1,值是1
16行:ifgt 5 如果int型大于零则回到第5行,1>0
5~9行:打印,1
12行:变量1自增-2,即1变为-1
15行:加载变量1,值是-1
16行:ifgt 5 如果int型大于零则回到第5行,-1 <0
19行:return main方法结束
for循环打印结果为5,3,1
foreach遍历
先new一个int[]数组,看看数据是如何存储的:
public class SimpleDemo { public static void main(String[] args) {
// TODO Auto-generated method stub
int[]arr = new int[];
arr[] = ;
arr[] = ;
arr[] = ;
}
}
Compiled from "SimpleDemo.java"
public class cn._012thDay._foreach.SimpleDemo {
public cn._012thDay._foreach.SimpleDemo();
Code:
: aload_0
: invokespecial # // Method java/lang/Object."<init>":()V
: return public static void main(java.lang.String[]);
Code:
: iconst_3
: newarray int
: astore_1
: aload_1
: iconst_0
: bipush
: iastore
: aload_1
: iconst_1
: bipush
: iastore
: aload_1
: iconst_2
: bipush
: iastore
: return
}
前3行创建基本类型(int)数组,长度为3,存入本地引用型变量1
将索引为0的位置压入10并存储
将索引为1的位置压入20并存储
将索引为2的位置压入30并存储
接下来开始遍历,加入for循环:
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
Compiled from "SimpleDemo.java"
public class cn._012thDay._foreach.SimpleDemo {
public cn._012thDay._foreach.SimpleDemo();
Code:
: aload_0
: invokespecial # // Method java/lang/Object."<init>":()V
: return public static void main(java.lang.String[]);
Code:
: iconst_3
: newarray int
: astore_1
: aload_1
: iconst_0
: bipush
: iastore
: aload_1
: iconst_1
: bipush
: iastore
: aload_1
: iconst_2
: bipush
: iastore
: iconst_0
: istore_2
: goto
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: aload_1
: iload_2
: iaload
: invokevirtual # // Method java/io/PrintStream.println:(I)V
: iinc ,
: iload_2
: aload_1
: arraylength
: if_icmplt
: return
}
上面代码大家应该不那么陌生了,前面18行存入数组,第19行开始创建了一个新的变量int型值为0,存入变量2.然后用变量2和数组长度作比较,小于数组长度就回到第24行打印,这是一个典型的for循环.
整个遍历中不考虑超类的加载总共创建了两个本地变量,即arr[3]和int i,用arr[3]的长度3和i进行比较,符合条件输出arr[i].输出结果为10,20,30
下面终于轮到我们的主角foreach登场了,删除for循环,新增foreach迭代:
for (int item : arr) {
System.out.println(item);
}
Compiled from "SimpleDemo.java"
public class cn._012thDay._foreach.SimpleDemo {
public cn._012thDay._foreach.SimpleDemo();
Code:
: aload_0
: invokespecial # // Method java/lang/Object."<init>":()V
: return public static void main(java.lang.String[]);
Code:
: iconst_3
: newarray int
: astore_1
: aload_1
: iconst_0
: bipush
: iastore
: aload_1
: iconst_1
: bipush
: iastore
: aload_1
: iconst_2
: bipush
: iastore
: aload_1 load local1 :
: dup copy
: astore int[] local5 = local1
: arraylength
: istore int local4 =
: iconst_0
: istore_3 int local3 =
: goto
: aload load local5 : int[]
: iload_3 load local3 : ..
: iaload arr[..]进栈
: istore_2 int local2 = arr[..]
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: iload_2 load local2 :arr[..]
: invokevirtual # // Method java/io/PrintStream.println:(I)V
: iinc , local3 +=
: iload_3 load local3 :..
: iload load local4 :
: if_icmplt local3 < local4 ? go line31:next line
: return
}
以上代码我加入了注释,这里大家应该可以看懂了,不考虑超类加载,foreach总共创建了5个本地变量:
local1是原始数组,引用类型
local5是原始数组副本,引用类型
local4是副本数组长度,int类型
local3是0,int类型
local2是arr[i]的副本,int类型
总结:
1.for循环和foreach循环底层创建变量数不同,对于遍历int[]类型数组,for循环底层创建2个本地变量,而foreach底层创建5个本地变量;
2.for循环直接对数组进行操作,foreach对数组副本进行操作;
由于foreach是对数组副本操作,开发中可能导致的问题:
附上java代码和javap -c代码
public class ForeachDemo { public static void main(String[] args) {
// TODO Auto-generated method stub String[] s1 = new String[];
for (String item : s1) {//这里的s1实际是s1副本
item = new String("b");
System.out.println(item);//这里可以把副本中的每项打印出来
}
print(s1);//打印s1是null,因为s1在内存地址中没有任何变化 } private static void print(String[] s) {
// TODO Auto-generated method stub
for (int i = ; i < s.length; i++) {
System.out.print(s[i]+" ");
}
} }
Compiled from "ForeachDemo.java"
public class cn._012thDay._foreach.ForeachDemo {
public cn._012thDay._foreach.ForeachDemo();
Code:
: aload_0
: invokespecial # // Method java/lang/Object."<init>":()V
: return public static void main(java.lang.String[]);
Code:
: iconst_3
: anewarray # // class java/lang/String
: astore_1
: aload_1
: dup
: astore
: arraylength
: istore
: iconst_0
: istore_3
: goto
: aload
: iload_3
: aaload
: astore_2
: new # // class java/lang/String
: dup
: ldc # // String b
: invokespecial # // Method java/lang/String."<init>":(Ljava/lang/String;)V
: astore_2
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: aload_2
: invokevirtual # // Method java/io/PrintStream.println:(Ljava/lang/String;)V
: iinc ,
: iload_3
: iload
: if_icmplt
: aload_1
: invokestatic # // Method print:([Ljava/lang/String;)V
: return
}
javap -c代码第7行新建了String[]数组副本变量5,之后一直在对副本进行操作,直到48行aload_1,然后打印,此时不难看出foreach中进行的所有操作都没有对本地变量1(即原数组)的值产生任何影响.
所以main方法最后一行打印数组s1,其结果一定是3个null
foreach底层机制的更多相关文章
- [转]STL 容器一些底层机制
1.vector 容器 vector 的数据安排以及操作方式,与 array 非常相似.两者的唯一区别在于空间的运用的灵活性.array 是静态空间,一旦配置了就不能改变,vector 是动态数组.在 ...
- C++ STL容器底层机制
1.vector容器 vector的数据安排以及操作方式,与array非常相似.两者的唯一区别在于空间的运用的灵活性.array是静态空间,一旦配置了就不能改变.vector是动态空间,随着元素的加入 ...
- 探索C++的底层机制
探索C++的底层机制 在看这篇文章之前,请你先要明白一点:那就是c++为我们所提供的各种存取控制仅仅是在编译阶段给我们的限制,也就是说是编译器确保了你在完成任务之前的正确行为,如果你的行为不正确,那么 ...
- tensorflow入门教程和底层机制简单解说——本质就是图计算,自动寻找依赖,想想spark机制就明白了
简介 本章的目的是让你了解和运行 TensorFlow! 在开始之前, 让我们先看一段使用 Python API 撰写的 TensorFlow 示例代码, 让你对将要学习的内容有初步的印象. 这段很短 ...
- 20191031:Python底层机制
20191031:Python底层机制 python底层从3个方面来说,分别是: 引用计数机制 垃圾回收机制 内存池机制 引用计数机制 使用引用计数来追踪内存中的对象,所有对象都有引用计数,并且这个引 ...
- php-浅谈php底层机制
php-浅谈php底层机制 1. PHP的设计理念及特点 多进程模型:由于PHP是多进程模型,不同请求间互不干涉,这样保证了一个请求挂掉不会对全盘服务造成影响,当然,随着时代发展,PHP也早已支持多线 ...
- ②NuPlayer播放框架之ALooper-AHandler-AMessage底层机制分析
[时间:2016-09] [状态:Open] [关键词:android,NuPlayer,开源播放器,播放框架,ALooper,AHandler,AMessage] 前文中提到过NuPlayer基于S ...
- Java 底层机制(JVM/堆/栈/方法区/GC/类加载)
转载:https://www.jianshu.com/p/ae97b692614e?from=timeline JVM体系结构 JVM是一种解释执行class文件的规范技术. JVM体系结构 我翻 ...
- (转载)JVM实现synchronized的底层机制
目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea.本文并不比较synchronized与Loc ...
随机推荐
- js面向对象-原型链
var Person = function (name) { this.name = name; } Person.prototype.say = function () { console.log( ...
- Spring+SpringMVC+MyBatis+easyUI整合优化篇(四)单元测试实例
日常啰嗦 前一篇文章<Spring+SpringMVC+MyBatis+easyUI整合优化篇(三)代码测试>讲了不为和不能两个状态,针对不为,只能自己调整心态了,而对于不能,本文会结合一 ...
- [SinGuLaRiTy] 平衡树
[SinGuLaRiTy-1009] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. 二叉查找树 二叉查找树是指具有下列性质的非空二叉树: ⑴ ...
- MySQL存储过程--带参数报错1064
DELIMITER $$ USE `student`$$ DROP PROCEDURE IF EXISTS `sync_student`$$ CREATE DEFINER=`student`@`%` ...
- 基于WebForm和Bootstrap的权限框架解决方案 一.PQGRID的使用
天天打游戏也不是个事,就写一套权限框架吧,我的要求是即漂亮美观大方上档次,又要实用易用接地气. 按理来说应该先设计数据库在来秀的,但是人生就是这么随意,先搭个框子吧, 这一篇的重点是pqgrid的介绍 ...
- Spring Dubbo 开发笔记
第一节:概述 Spring-Dubbo 是我自己写的一个基于spring-boot和dubbo,目的是使用Spring boot的风格来使用dubbo.(即可以了解Spring boot的启动过程又可 ...
- JavaEE开发之SpringMVC中的路由配置及参数传递详解
在之前我们使用Swift的Perfect框架来开发服务端程序时,聊到了Perfect中的路由配置.而在SpringMVC中的路由配置与其也是大同小异的.说到路由,其实就是将URL映射到Java的具体类 ...
- mapTask并行度优化及源码分析
mapTask并行度的决定机制 一个job的map阶段并行度由客户端在提交job时决定,而客户端对map阶段并行度的规划的基本逻辑为:将待处理数据执行逻辑切片(即按照一个特定切片大小,将待处理数据划分 ...
- 使用canvas实现擦除效果
HTML5 的 canvas 元素使用 JavaScript 在网页上绘制图像.画布是一个矩形区域,您可以控制其每一像素.canvas 拥有多种绘制路径.矩形.圆形.字符以及添加图像的方法. html ...
- 一条SQL搞定信息增益的计算
欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 周东谕,2011年加入腾讯,现任职于腾讯互娱运营部数据中心,主要从事游戏相关的数据分析和挖掘工作. 信息增益原 ...