foreach底层机制

简单例子

直接了解foreach底层有些困难,我们需要从更简单的例子着手.下面上一个简单例子:

1 public class Simple {
2
3     public static void main(String[] args) {
4         int i = 5;
5         System.out.println(i);
6     }
7 }

找到其字节码文件所在目录并在目录下打开终端(Windows系统是在目录下shift+鼠标右键选择在此打开powershell窗口)

输入指令:javac -Simple.class >SimpleRunc

目录中得到SimpleRunc文件,打开它,会看到如下代码(里面有我的注释):

 1 Compiled from "Simple.java"
 2 public class cn._012thDay._foreach.Simple {
 3   public cn._012thDay._foreach.Simple();
 4     Code:
 5        0: aload_0                将第一个引用型本地变量推送至栈顶;
 6        1: invokespecial #8                  // Method java/lang/Object."<init>":()V
 7                         调用超类构造方法;
 8        4: return
 9
10   public static void main(java.lang.String[]);
11     Code:
12        0: iconst_5                将int型5推送至栈顶;
13        1: istore_1                将栈顶int型数据存入第二个本地变量;
14                         此处推测:第一个本地变量是超类;
15
16
17        2: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
18                         获取本地静态域并将其压入栈顶;即获取静态属性压入栈顶
19        5: iload_1                将第二个int型本地变量推送至栈顶;
20        6: invokevirtual #22                 // Method java/io/PrintStream.println:(I)V
21                         调用实例方法;
22     获取类属性,加载入栈,打印栈顶,即5
23
24        9: return
25 }

如果不懂指令意思,可查询JVM指令表.这里我说明一下步骤:

第一步:加载超类Object类

第二步:将int类型的5压入栈顶,然后将5存入本地变量1

第三部:获取静态属性

第四步:加载本地变量1,即将5推送至栈顶

第五步:打印

for循环

1 public class ForSimple {
2
3     public static void main(String[] args) {
4         for (int i = 5; i > 0; i-=2) {
5             System.out.println(i);
6         }
7     }
8 }

其实这个例子foreach就做不了,因为foreach遍历必须要有一个数组.

javap -c:

Compiled from "ForSimple.java"
public class cn._012thDay._foreach.ForSimple {
  public cn._012thDay._foreach.ForSimple();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_5
       1: istore_1
       2: goto          15
       5: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
       8: iload_1
       9: invokevirtual #22                 // Method java/io/PrintStream.println:(I)V
      12: iinc          1, -2
      15: iload_1
      16: ifgt          5
      19: 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[]数组,看看数据是如何存储的:

 1 public class SimpleDemo {
 2
 3     public static void main(String[] args) {
 4         // TODO Auto-generated method stub
 5         int[]arr = new int[3];
 6         arr[0] = 10;
 7         arr[1] = 20;
 8         arr[2] = 30;
 9     }
10 }

Compiled from "SimpleDemo.java"
public class cn._012thDay._foreach.SimpleDemo {
  public cn._012thDay._foreach.SimpleDemo();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: newarray       int
       3: astore_1
       4: aload_1
       5: iconst_0
       6: bipush        10
       8: iastore
       9: aload_1
      10: iconst_1
      11: bipush        20
      13: iastore
      14: aload_1
      15: iconst_2
      16: bipush        30
      18: iastore
      19: 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:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: newarray       int
       3: astore_1
       4: aload_1
       5: iconst_0
       6: bipush        10
       8: iastore
       9: aload_1
      10: iconst_1
      11: bipush        20
      13: iastore
      14: aload_1
      15: iconst_2
      16: bipush        30
      18: iastore
      19: iconst_0
      20: istore_2
      21: goto          36
      24: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      27: aload_1
      28: iload_2
      29: iaload
      30: invokevirtual #22                 // Method java/io/PrintStream.println:(I)V
      33: iinc          2, 1
      36: iload_2
      37: aload_1
      38: arraylength
      39: if_icmplt     24
      42: 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:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: newarray       int
       3: astore_1
       4: aload_1
       5: iconst_0
       6: bipush        10
       8: iastore
       9: aload_1
      10: iconst_1
      11: bipush        20
      13: iastore
      14: aload_1
      15: iconst_2
      16: bipush        30
      18: iastore
      19: aload_1            load local1 :0
      20: dup                copy
      21: astore        5        int[] local5 = local1
      23: arraylength            3
      24: istore        4        int local4 = 3
      26: iconst_0            0
      27: istore_3            int local3 = 0
      28: goto          46
      31: aload         5        load local5 : int[3]
      33: iload_3            load local3 : 0..
      34: iaload            arr[0..]进栈
      35: istore_2            int local2 = arr[0..]
      36: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      39: iload_2            load local2 :arr[0..]
      40: invokevirtual #22                 // Method java/io/PrintStream.println:(I)V
      43: iinc          3, 1        local3 +=1
      46: iload_3            load local3 :0..
      47: iload         4        load local4 :3
      49: if_icmplt     31        local3 < local4 ? go line31:next line
      52: 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代码

 1 public class ForeachDemo {
 2
 3     public static void main(String[] args) {
 4         // TODO Auto-generated method stub
 5
 6         String[] s1 = new String[3];
 7         for (String item : s1) {//这里的s1实际是s1副本
 8             item = new String("b");
 9             System.out.println(item);//这里可以把副本中的每项打印出来
10         }
11         print(s1);//打印s1是null,因为s1在内存地址中没有任何变化
12
13     }
14
15     private static void print(String[] s) {
16         // TODO Auto-generated method stub
17         for (int i = 0; i < s.length; i++) {
18             System.out.print(s[i]+" ");
19         }
20     }
21
22 }

Compiled from "ForeachDemo.java"
public class cn._012thDay._foreach.ForeachDemo {
  public cn._012thDay._foreach.ForeachDemo();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: anewarray     #16                 // class java/lang/String
       4: astore_1
       5: aload_1
       6: dup
       7: astore        5
       9: arraylength
      10: istore        4
      12: iconst_0
      13: istore_3
      14: goto          42
      17: aload         5
      19: iload_3
      20: aaload
      21: astore_2
      22: new           #16                 // class java/lang/String
      25: dup
      26: ldc           #18                 // String b
      28: invokespecial #20                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
      31: astore_2
      32: getstatic     #23                 // Field java/lang/System.out:Ljava/io/PrintStream;
      35: aload_2
      36: invokevirtual #29                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: iinc          3, 1
      42: iload_3
      43: iload         4
      45: if_icmplt     17
      48: aload_1
      49: invokestatic  #34                 // Method print:([Ljava/lang/String;)V
      52: return
}

javap -c代码第7行新建了String[]数组副本变量5,之后一直在对副本进行操作,直到48行aload_1,然后打印,此时不难看出foreach中进行的所有操作都没有对本地变量1(即原数组)的值产生任何影响.

所以main方法最后一行打印数组s1,其结果一定是3个null

时间: 2024-10-15 08:26:51

foreach底层机制的相关文章

关联容器(底层机制) — hashtable

C++ 11已将哈希表纳入了标准之列.hashtable是hash_set.hash_map.hash_multiset.hash_multimap的底层机制,即这四种容器中都包含一个hashtable. 解决碰撞问题的办法有许多,线性探测.二次探测.开链等等.SGI STL的hashtable采用的开链方法,每个hash table中的元素用vector承载,每个元素称为桶(bucket),一个桶指向一个存储了实际元素的链表(list),链表节点(node)结构如下: template <cl

关联容器(底层机制) — 红黑树

set.map.multiset.multimap四种关联式容器的内部都是由红黑树实现的.在STL中红黑树是一个不给外界使用的独立容器.既然是容器,那么就会分配内存空间(节点),内部也会存在迭代器.关于红黑树的一些性质,可以参考"数据结构"中的笔记,这里只记录STL中的红黑树是如何实现的. 和slist一样,红黑树的节点和迭代器均采用了双层结构: 节点:__rb_tree_node继承自__rb_tree_node_base 迭代器:__rb_tree_iterator继承自__rb_

探索C++的底层机制

探索C++的底层机制 在看这篇文章之前,请你先要明白一点:那就是c++为我们所提供的各种存取控制仅仅是在编译阶段给我们的限制,也就是说是编译器确保了你在完成任务之前的正确行为,如果你的行为不正确,那么你休想构造出任何可执行程序来.但如果真正到了产生可执行代码阶段,无论是c,c++,还是pascal,大家都一样,你认为c和c++编译器产生的机器代码会有所不同吗,你认为c++产生的机器代码会有访问限制吗?那么你错了.什么const,private,统统没有(const变量或许会放入只读数据段),它不

C++ STL容器底层机制

1.vector容器 vector的数据安排以及操作方式,与array非常相似.两者的唯一区别在于空间的运用的灵活性.array是静态空间,一旦配置了就不能改变.vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素.因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必因为害怕空间不足而一开始要求一个大块的array. vector动态增加大小,并不是在原空间之后持续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配

Asp.net底层机制

Asp.net底层就是用户通过输入网址,然后请求IIs服务器的流程,在这个过程中有一个重要的部件就是ISAPI,这是一个底层的win32API,在扩展方面比较困难,多用于接口之间的桥接,.net和IIS通过IISAPI进行交互的,IIS中后缀名.aspx,通过应用程序扩展映射到.net的ISAPI扩展的dll,这个dll就是aspnet_isapi.dll,访问都是通过这个dll中相应的映射完成的. 1.通过asp.net的映射机制使用后缀名从ISAPI中获取到接收的请求,并将请求的路由到相应的

(转载)JVM实现synchronized的底层机制

目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea.本文并不比较synchronized与Lock孰优孰劣,只是介绍二者的实现原理. 数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖特殊的CPU指令,大家可能会进一步追问:JVM底层又是如何实现synchronized的? 本文所指说的JVM是指Hotspot

windows消息钩子注册底层机制浅析

标 题: [原创]消息钩子注册浅析 作 者: RootSuLe 时 间: 2011-06-18,23:10:34 链 接: http://bbs.pediy.com/showthread.php?t=135702 windows消息钩子很古老,但目前仍有很多地方需要用到,简单跟踪了一下函数执行流程,函数用法如下(MSDN): 函数功能:该函数将一个应用程序定义的挂钩处理过程安装到挂钩链中去,您可以通过安装挂钩处理过程来对系统的某些类型事件进行监控,这些事件与某个特定的线程或系统中的所有事件相关.

duilib底层机制剖析:窗体类与窗体句柄的关联

转载请说明原出处,谢谢~~ 看到群里朋友有人讨论WTL中的thunk技术,让我联想到了duilib的类似技术.这些技术都是为了解决c++封装的窗体类与窗体句柄的关联问题. 这里是三篇关于thunk技术的博客,不懂的朋友可以先看一下: WTL学习之旅(三)WTL中 Thunk技术本质(含代码) 深入剖析WTL-WTL框架窗口分析 (5) 学习下 WTL 的 thunk 我这里直接引用其他博客的一部分文字来说明窗体类与窗体句柄关联的重要性和相关的问题,然后说明一下duilib中的解决方法: ----

JAVA IO编程 IO多路复用底层机制

前面IO模型中有提到IO多路复用,这里介绍下linux下的三种机制(基于多路复用模型的) select,poll,epoll 反应器模式Reactor和Proactor模式 两者的主要区别是就是真正的读取和写入操作是由谁来完成的 Reactor中需要应用程序自己读取或者写入数据 Proactor模式,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO的设备 Reactor(反应器模式) 说明你可以进行读写操作了,关注的是IO操作的就