精华内容
下载资源
问答
  • 一、增强for循环 1. 三种常用for循环 // 普通for循环遍历 for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i) + ","); } // 迭代器循环遍历 Iterator iterator = list.iterator(); while...

    一、增强for循环

    1. 三种常用for循环

    // 普通for循环遍历
    for (int i = 0; i < list.size(); i++) {
       System.out.print(list.get(i) + ",");
    }
    // 迭代器循环遍历
    Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
       System.out.print(iterator.next() + ",");
    }
    // 增强for循环
    for (Integer i : list) {
       System.out.print(i + ",");
    }

    2. 增强for循环实现原理

    编译前:

    for (Integer i : list) {
       System.out.print(i + ",");
    }

    编译后:

    Integer i;
    for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(i)){
       i = (Integer)iterator.next();        
    }

    源码解析:

    Integer i; // 定义一个临时变量i
    Iterator iterator = list.iterator(); // 获取List的迭代器
    iterator.hasNext(); // 判断迭代器中是否有未遍历过的元素
    i = (Integer)iterator.next(); // 获取第一个未遍历的元素,赋值给临时变量i
    System.out.println(i) // 输出临时变量i的值

    通过反编译源码,我们看到,其实JAVA中的增强for循环底层是通过迭代器模式来实现的。

    所以以下的代码:

    Iterator<Map.Entry<String, Integer>> entries = myMap.entrySet().iterator();
    while (entries.hasNext()) {
        Map.Entry<String, Integer> entry = entries.next();
        String key = entry.getKey();
        Integer value = entry.getValue();
    }

     可以简化为:

    for(Map.Entry<String, Integer> entry : myMap.entrySet()) {
        String qingActivityId = entry.getKey();
        Integer value = entry.getValue();
    }

     


    3. 注意:增强for循环可能遇到的坑

    既然增强for循环通过迭代器实现,那么必然有迭代器的特性。

    Java中有fail-fast机制。在使用迭代器遍历元素的时候,在对集合进行删除的时候一定要注意,使用不当有可能发生ConcurrentModificationException,这是一种运行时异常,编译期并不会发生。只有在程序真正运行时才会爆发。

    // 代码示例
    for (UserInfo user : userInfos) {    
       if (user.getId() == 2)     
          userInfos.remove(user);    
    }

     会抛出ConcurrentModificationException异常。

    Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出
    java.util.ConcurrentModificationException异常。

    所以 Iterator 在执行的时候是不允许被迭代的对象被改变的

    删除对象的正确做法:

    你可以使用 Iterator 本身的方法 remove() 来删除对象,Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

    正确的在遍历的同时删除元素的示例:

    Iterator<UserInfo> userIterator = users.iterator();    
    while (userIterator.hasNext()) {    
       UserInfo userInfo = userIterator.next();    
       if (userInfo.getId() == 2)    
           userIterator.remove();//这里要使用Iterator的remove方法移除当前对象,如果使用List的remove方法,则同样会出现ConcurrentModificationException    
    }

     

    二、for循环实战性能优化

    循环结构让我们操作数组、集合和其他一些有规律的事物变得更加的方便,但是如果我们在实际开发当中运用不合理,可能会给程序的性能带来很大的影响。所以我们还是需要掌握一些技巧来优化我们的代码的。

    1. 嵌套循环

    1.1 代码示例

    优化前代码示例

    Long stratTime = System.nanoTime();  
    for (int i = 0; i < 10000; i++) {  
        for (int j = 0; j < 10; j++) {  
              
        }  
    }  
    Long endTime = System.nanoTime();  
    System.out.println("外大内小耗时:"+ (endTime - stratTime));   

    优化后代码示例

    Long  stratTime = System.nanoTime();  
    for (int i = 0; i <10 ; i++) {  
        for (int j = 0; j < 10000; j++) {  
              
        }  
    }  
    Long  endTime = System.nanoTime();  
    System.out.println("外小内大耗时:"+(endTime - stratTime));  

    运行结果:

    外大内小耗时:1957590
    外小内大耗时:1228223

    由运行结果来看采用外大内小的方式性能差距还是比较大的。

    1.2 原理

    如果遇到分支结构,就可以利用分支目标缓冲器预测并读取指令的目标地址。分支目标缓冲器在程序运行时将动态记录和调整转移指令的目标地址,可以记录多个地址,对其进行表格化管理。当发生转移时,如果分支目标缓冲器中有记录,下一条指令在取指令阶段就会将其作为目标地址。如果记录地址等于实际目标地址,则并行成功;如果记录地址不等于实际目标地址,则流水线被冲洗。同一个分支,多次预测失败,则更新记录的目标地址。因此,分支预测属于“经验主义”或“机会主义”,会存在一定误测。
    ————摘抄来源<<C++反汇编与逆向分析技术解密>> 4.4.2 分支优化规则

    1.3 原理解析

    // 外小内大
    for (int i = 0; i <10 ; i++) {  
        // 下面每次循环会预测成功9999次
        // 第1次没有预测,最后退出循环时预测失败1次
        // 这样的过程重复10次
        for (int j = 0; j < 10000; j++) {  
              a[i][j]++;
        }  
    }  
    // 外大内小
    for (int i = 0; i < 10000; i++) {  
        // 下面每次循环会预测成功9次
        // 第1次没有预测,最后退出循环时预测失败1次
        // 这样的过程重复10000次
        for (int j = 0; j < 10; j++) {  
              a[i][j]++;
        }  
    }  

    2. 消除循环终止判断时的方法调用

    2.1 代码示例

    未优化前代码示例

    Long stratTime = System.nanoTime();  
    for (int i = 0; i < list.size(); i++) {  
          
    }  
    Long  endTime = System.nanoTime();  
    System.out.println("未优化list耗时:"+(endTime - stratTime));  

    优化后代码示例

    Long  stratTime = System.nanoTime();  
    int size = list.size();  
    for (int i = 0; i < size; i++) {  
          
    }  
    Long  endTime = System.nanoTime();  
    System.out.println("优化list耗时:"+(endTime - stratTime)); 

    运行结果

    未优化list耗时:27375  
    优化list耗时:2444

    2.2原理

    list.size()每次循环都会被执行一次,这无疑会影响程序的性能,所以应该将其放到循环外面,用一个变量来代替,优化前后的对比也很明显。


    3. 异常捕获

    3.1 代码示例

    优化前代码示例

    Long stratTime = System.nanoTime();  
    for (int i = 0; i < 10000000; i++) {  
        try {  
        } catch (Exception e) {  
        }  
    }  
    Long  endTime = System.nanoTime();  
    System.out.println("在内部捕获异常耗时:"+(endTime - stratTime));  

    优化后代码示例

    Long  stratTime = System.nanoTime();  
    try {  
        for (int i = 0; i < 10000000; i++) {  
        }  
    } catch (Exception e) {  
      
    }  
    Long  endTime = System.nanoTime();  
    System.out.println("在外部捕获异常耗时:"+(endTime - stratTime));  

    运行结果

    在内部捕获异常耗时:12150142  
    在外部捕获异常耗时:1955  

    3.2 总结

    捕获异常是很耗资源的,所以不要讲try catch放到循环内部,优化后同样有好几个数量级的提升。

     


    来源于:

    https://www.jianshu.com/p/f7c82aa62439

    展开全文
  • 前言 循环就是让我们的程序...其中,for循环就是循环结构的一种,另外还有while循环和do-while循环语句。但是for循环是开发者最常用的开发方式。 一、增强for循环1. 三种常用for循环#普通for循环遍历 for (int i = 0;

    IT实战联盟博客:http://blog.100boot.cn
    IT实战联盟.jpg

    前言

    循环就是让我们的程序重复地执行某些业务。在程序设计时,需要处理大量的重复动作,采用循环结构可以降低程序书写的长度和复杂度,可使复杂问题简单化,提高程序的可读性和执行速度。其中,for循环就是循环结构的一种,另外还有while循环和do-while循环语句。但是for循环是开发者最常用的开发方式。

    for循环.jpg

    一、增强for循环

    1. 三种常用for循环

    # 普通for循环遍历
    for (int i = 0; i < list.size(); i++) {
       System.out.print(list.get(i) + ",");
    }
    #迭代器循环遍历
    Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
       System.out.print(iterator.next() + ",");
    }
    #增强for循环
    for (Integer i : list) {
       System.out.print(i + ",");
    }
    

    2. 增强for循环实现原理

    编译前

    for (Integer i : list) {
       System.out.print(i + ",");
    }
    

    编译后

    Integer i;
    for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(i)){
       i = (Integer)iterator.next();        
    }
    

    源码解析

    Integer i; 定义一个临时变量i
    Iterator iterator = list.iterator(); 获取List的迭代器
    iterator.hasNext(); 判断迭代器中是否有未遍历过的元素
    i = (Integer)iterator.next(); 获取第一个未遍历的元素,赋值给临时变量i
    System.out.println(i) 输出临时变量i的值
    

    通过反编译源码,我们看到,其实JAVA中的增强for循环底层是通过迭代器模式来实现的。

    ####3. 注意:增强for循环可能遇到的坑
    既然增强for循环通过迭代器实现,那么必然有迭代器的特性。

    Java中有fail-fast机制。在使用迭代器遍历元素的时候,在对集合进行删除的时候一定要注意,使用不当有可能发生ConcurrentModificationException,这是一种运行时异常,编译期并不会发生。只有在程序真正运行时才会爆发。

    #代码示例
    for (UserInfo user : userInfos) {    
       if (user.getId() == 2)     
          userInfos.remove(user);    
    }
    

    会抛出ConcurrentModificationException异常。

    Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出
    java.util.ConcurrentModificationException异常。

    所以 Iterator 在执行的时候是不允许被迭代的对象被改变的。

    但你可以使用 Iterator 本身的方法 remove() 来删除对象,Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

    正确的在遍历的同时删除元素的示例:

    Iterator<UserInfo> userIterator = users.iterator();    
    while (userIterator.hasNext()) {    
       UserInfo userInfo = userIterator.next();    
       if (userInfo.getId() == 2)    
           userIterator.remove();//这里要使用Iterator的remove方法移除当前对象,如果使用List的remove方法,则同样会出现ConcurrentModificationException    
    }
    

    二、for循环实战性能优化

    循环结构让我们操作数组、集合和其他一些有规律的事物变得更加的方便,但是如果我们在实际开发当中运用不合理,可能会给程序的性能带来很大的影响。所以我们还是需要掌握一些技巧来优化我们的代码的。

    1. 嵌套循环

    1.1 代码示例

    优化前代码示例

    Long stratTime = System.nanoTime();  
    for (int i = 0; i < 10000; i++) {  
        for (int j = 0; j < 10; j++) {  
              
        }  
    }  
    Long endTime = System.nanoTime();  
    System.out.println("外大内小耗时:"+ (endTime - stratTime));        
    

    优化后代码示例

    Long  stratTime = System.nanoTime();  
    for (int i = 0; i <10 ; i++) {  
        for (int j = 0; j < 10000; j++) {  
              
        }  
    }  
    Long  endTime = System.nanoTime();  
    System.out.println("外小内大耗时:"+(endTime - stratTime));   
    

    运行结果:

    外大内小耗时:1957590
    外小内大耗时:1228223
    

    由运行结果来看采用外大内小的方式性能差距还是比较大的。

    1.2 原理

    如果遇到分支结构,就可以利用分支目标缓冲器预测并读取指令的目标地址。分支目标缓冲器在程序运行时将动态记录和调整转移指令的目标地址,可以记录多个地址,对其进行表格化管理。当发生转移时,如果分支目标缓冲器中有记录,下一条指令在取指令阶段就会将其作为目标地址。如果记录地址等于实际目标地址,则并行成功;如果记录地址不等于实际目标地址,则流水线被冲洗。同一个分支,多次预测失败,则更新记录的目标地址。因此,分支预测属于“经验主义”或“机会主义”,会存在一定误测。
    ————摘抄来源<<C++反汇编与逆向分析技术解密>> 4.4.2 分支优化规则

    1.3 原理解析
    #外小内大
    for (int i = 0; i <10 ; i++) {  
        #下面每次循环会预测成功9999次
        #第1次没有预测,最后退出循环时预测失败1次
        #这样的过程重复10次
        for (int j = 0; j < 10000; j++) {  
              a[i][j]++;
        }  
    }  
    #外大内小
    for (int i = 0; i < 10000; i++) {  
        #下面每次循环会预测成功9次
        #第1次没有预测,最后退出循环时预测失败1次
        #这样的过程重复10000次
        for (int j = 0; j < 10; j++) {  
              a[i][j]++;
        }  
    }  
    

    2. 消除循环终止判断时的方法调用

    2.1 代码示例

    未优化前代码示例

    Long stratTime = System.nanoTime();  
    for (int i = 0; i < list.size(); i++) {  
          
    }  
    Long  endTime = System.nanoTime();  
    System.out.println("未优化list耗时:"+(endTime - stratTime));  
    

    优化后代码示例

    Long  stratTime = System.nanoTime();  
    int size = list.size();  
    for (int i = 0; i < size; i++) {  
          
    }  
    Long  endTime = System.nanoTime();  
    System.out.println("优化list耗时:"+(endTime - stratTime));  
    

    运行结果

    未优化list耗时:27375  
    优化list耗时:2444  
    
    2.2原理
    list.size()每次循环都会被执行一次,这无疑会影响程序的性能,所以应该将其放到循环外面,用一个变量来代替,优化前后的对比也很明显。
    

    3. 异常捕获

    3.1 代码示例

    优化前代码示例

    Long stratTime = System.nanoTime();  
    for (int i = 0; i < 10000000; i++) {  
        try {  
        } catch (Exception e) {  
        }  
    }  
    Long  endTime = System.nanoTime();  
    System.out.println("在内部捕获异常耗时:"+(endTime - stratTime));  
    

    优化后代码示例

    Long  stratTime = System.nanoTime();  
    try {  
        for (int i = 0; i < 10000000; i++) {  
        }  
    } catch (Exception e) {  
      
    }  
    Long  endTime = System.nanoTime();  
    System.out.println("在外部捕获异常耗时:"+(endTime - stratTime));  
    

    运行结果

    在内部捕获异常耗时:12150142  
    在外部捕获异常耗时:1955  
    
    3.2 总结

    捕获异常是很耗资源的,所以不要讲try catch放到循环内部,优化后同样有好几个数量级的提升。

    结尾

    性能优化的内容有很多,代码优化只是其中一小部分,我们在日常开发中应养成良好的编码习惯。接下来会跟大家探讨更多关于性能优化的内容,希望大家积极交流指导。

    关注我们

    如果需要源码可以关注“IT实战联盟”公号并留言(源码名称+邮箱),小萌看到后会联系作者发送到邮箱,也可以加入交流群和作者互撩哦~~~

    IT实战联盟博客:http://blog.100boot.cn

    展开全文
  • for-each循环是jdk1.5引入的新的语法功能。并不是所有东西都可以使用这个循环的。可以看下Iterable接口的注释,它说明了除了数组外,其他类想要使用for-each循环必须实现这个接口。这一点表明除了数组外的for-each...

    for-each循环是jdk1.5引入的新的语法功能。并不是所有东西都可以使用这个循环的。可以看下Iterable接口的注释,它说明了除了数组外,其他类想要使用for-each循环必须实现这个接口。这一点表明除了数组外的for-each可能底层是由迭代器实现的。

    Iterable接口在1.8之前只有一个方法,Iterator<T> iterator(),此方法返回一个迭代器。由于更早出现的Collection接口中早就有了这个同样的方法,所以只需要让Collection接口继承Iterable接口,基于Collection的集合类就可以不做任何更改就使用for-each循环。

    对于数组,因为数组不实现Iterable接口,它的for-each实现原理应该和Collection不一样。

    下面就通过分析下不同方式编译后的字节码,简单研究下for-each的的底层原理。

    一、数组的for-each
    下面是的两个很简单的类,可以看出它们的功能是一样的。Java环境使用的是jdk1.8_111。
    package iter;
    
    public class TestArray {
        public static void main(String[] args) {
            //String[] a = {"a", "b", "c"};
            long[] a = {2L, 3L, 5L};
            for (long i : a) {
                System.err.println(i);
            }
        }
    }
    
    package iter;
    
    public class TestArrayFor {
        public static void main(String[] args) {
            //String[] a = {"a", "b", "c"};
            long[] a = {2L, 3L, 5L};
            for (int i = 0, len = a.length; i < len; i++) {
                System.err.println(a[i]);
            }
        }
    }
    TestArray使用for-each,TestArrayFor使用传统for循环,使用long数组是为了字节码中好区分int/long。
    用javap -c看下两个类的字节码操作,保存成了文本,具体情况如下。
    Compiled from "TestArray.java"
    public class iter.TestArray {
      public iter.TestArray();
        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       long
           3: dup
           4: iconst_0
           5: ldc2_w        #16                 // long 2l
           8: lastore
           9: dup
          10: iconst_1
          11: ldc2_w        #18                 // long 3l
          14: lastore
          15: dup
          16: iconst_2
          17: ldc2_w        #20                 // long 5l
          20: lastore
          21: astore_1           /* 0-21行,创建long数组a,并保存在线程的当前栈帧的局部变量表的第1格*/
          22: aload_1            /* 读取保存在线程的当前栈帧的局部变量表的第1格的对象的引用,就是读取数组a */
          23: dup                /* 把a的引用复制一遍并且再放到栈顶 */
          24: astore        6    /* 把栈顶的数存在线程的当前栈帧的局部变量表的第6格,就是生成a的一个值复制品b并存储起来,暂时不知道为什么这里要复制一次,后面的数组都还是用a表示 */
          26: arraylength        /* 获取数组长度a.length */
          27: istore        5    /* 把数组长度存储在线程的当前栈帧的局部变量表的第5格,22-27隐式执行了int len = a.length */
          29: iconst_0           /* 读取数字0(这个就是普通的0)到栈中 */
          30: istore        4    /* 把数字0放在线程的当前栈帧的局部变量表的第4格,29-30隐式执行了int i = 0 */
          32: goto          51   /* 无条件跳转到51那个地方,开始循环的代码 */
          35: aload         6    /* 读取数组a */
          37: iload         4    /* 读取i */
          39: laload             /* 读取a[i] */
          40: lstore_2           /* 把a[i]存在线程的当前栈帧的局部变量表的第2格 */
          41: getstatic     #22                 // Field java/lang/System.err:Ljava/io/PrintStream; /* 获取类的static属性,就是System.err */
          44: lload_2            /* 读取存在线程的当前栈帧的局部变量表的第2格的数据,就是读取a[i] */
          45: invokevirtual #28                 // Method java/io/PrintStream.println:(J)V          /* 执行虚拟机方法,30-36就是执行System.err.println(a[i]) */
          48: iinc          4, 1 /* 将第4格的数字加1,就是执行i++ */
          51: iload         4    /* 读取i */
          53: iload         5    /* 读取a.length */
          55: if_icmplt     35   /* 如果i < len,跳到标记35的那个地方,不满足就往下 */
          58: return
    }
    Compiled from "TestArrayFor.java"
    public class iter.TestArrayFor {
      public iter.TestArrayFor();
        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       long
           3: dup
           4: iconst_0
           5: ldc2_w        #16                 // long 2l
           8: lastore
           9: dup
          10: iconst_1
          11: ldc2_w        #18                 // long 3l
          14: lastore
          15: dup
          16: iconst_2
          17: ldc2_w        #20                 // long 5l
          20: lastore
          21: astore_1           /* 0-21行,创建long数组a,并保存在线程的当前栈帧的局部变量表的第1格*/
          22: iconst_0           /* 读取数字0(这个就是普通的0)到栈中 */
          23: istore_2           /* 将栈顶的数字0保存在第二个,22-23就是执行int i = 0; */
          24: aload_1            /* 读取保存在线程的当前栈帧的局部变量表的第1格的对象的引用,就是读取数组a */
          25: arraylength        /* 获取数组长度a.length */
          26: istore_3           /* 把数组长度保存在线程的当前栈帧的局部变量表的第3格,24-26就是执行int len = a.length */
          27: goto          42   /* 无条件跳到标记42的那个地方,开始循环的代码 */
          30: getstatic     #22                 // Field java/lang/System.err:Ljava/io/PrintStream; /* 获取类的static属性,就是System.err */
          33: aload_1            /* 读取数组a */
          34: iload_2            /* 读取i */
          35: laload             /* 读取a[i] */
          36: invokevirtual #28                 // Method java/io/PrintStream.println:(J)V          /* 执行虚拟机方法,30-36就是执行System.err.println(a[i]) */
          39: iinc          2, 1 /* 将第2格的数字加1,就是执行i++ */
          42: iload_2            /* 读取i */
          43: iload_3            /* 读取len */
          44: if_icmplt     30   /* 如果i < len,跳到标记30的那个地方,不满足就往下 */
          47: return
    }
    
    本人对照下字节码指令表,简单翻译了以下,都写在上面,还算是比较清楚。/**/中的就是本人的注释,//开头的是字节码自带的信息,这些信息不能完全算是注释吧,可以算是对字节码中出现的常量的一种直白翻译,让你看得懂这些常量代表什么。
    通过编译后的字节码可以看出,数组的for-each和普通的for循环底层原理是一样的,都是用的普通for循环的那一套。数组的for-each比普通for循环多一点点操作,理论上是要慢一点点,这个暂时也不知道是为什么。这也是语法糖的一些代价,语法越简单,反而越不好进行底层优化。不过这个慢一点那真是一点,在循环体比较复杂时,这个差距就更小了,所以基本上可以认为这两种方式效率一样。实际中根据自己的情况选择,如果需要显式使用下标,就用传统for循环,其他的都可以使用for-each循环。


    二、Collection的for-each
    还是先贴两段简单的对比的代码,代码逻辑一样。Java环境使用的是jdk1.8_111。
    package iter;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class TestFor {
        public static void main(String[] args) {
            List<String> listA = new ArrayList<String>();
            for(String str : listA) {
                System.err.println(str);
            }
        }
    }
    
    package iter;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class TestIter {
        public static void main(String[] args) {
            List<String> listA = new ArrayList<String>();
            for (Iterator<String> iter = listA.iterator(); iter.hasNext();) {
                String s = iter.next();
                System.err.println(s);
            }
        }
    }
    

    TestFor是for-each循环,TestIter是使用迭代器循环。
    还是跟数组的一样分析,贴下编译后的字节码。

    Compiled from "TestFor.java"
    public class iter.TestFor {
      public iter.TestFor();
        Code:
           0: aload_0
           1: invokespecial #8                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #16                 // class java/util/ArrayList
           3: dup
           4: invokespecial #18                 // Method java/util/ArrayList."<init>":()V
           7: astore_1
           8: aload_1
           9: invokeinterface #19,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
          14: astore_3
          15: goto          35
          18: aload_3
          19: invokeinterface #25,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
          24: checkcast     #31                 // class java/lang/String
          27: astore_2
          28: getstatic     #33                 // Field java/lang/System.err:Ljava/io/PrintStream;
          31: aload_2
          32: invokevirtual #39                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          35: aload_3
          36: invokeinterface #45,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
          41: ifne          18
          44: return
    }
    
    Compiled from "TestIter.java"
    public class iter.TestIter {
      public iter.TestIter();
        Code:
           0: aload_0
           1: invokespecial #8                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #16                 // class java/util/ArrayList
           3: dup
           4: invokespecial #18                 // Method java/util/ArrayList."<init>":()V
           7: astore_1
           8: aload_1
           9: invokeinterface #19,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
          14: astore_2
          15: goto          35
          18: aload_2
          19: invokeinterface #25,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
          24: checkcast     #31                 // class java/lang/String
          27: astore_3
          28: getstatic     #33                 // Field java/lang/System.err:Ljava/io/PrintStream;
          31: aload_3
          32: invokevirtual #39                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          35: aload_2
          36: invokeinterface #45,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
          41: ifne          18
          44: return
    }
    

    这两段字节码中自带的注释很多,基本上看得懂,就不添加注释了。
    两段字节码除了几个变量保存在线程的当前栈帧的局部变量表的索引(astore_n,这个n就是索引)不一样外,其余的都是一模一样的。不排除某次编译后连那几个索引值也一样,那就真一模一样了。字节码自带的注释都说了,Collection的for-each底层也是使用迭代器来实现的,两种方式可以说是完全等价的。

    对于实现了RandomAccess接口的实现类,因为它们的随机访问操作的时间复杂度为O(1),大多数情况使用传统for循环会比用迭代器循环(这里的迭代器也可以用for-each替换,上面说了它们底层整体是一样的)要快。至于这一点是为什么,可以看下ArrayList的源码。它的迭代器虽然也是通过下标直接访问elementData数组,但是迭代器多了很多方法调用以及其他的额外操作,现在很多编译器cpu也都会对传统for循环进行特别的优化,在这个层面十几个指令的差别就很大了,这些因素加在一起导致RandomAccess的迭代器比传统for循环要慢一些。对于ArrayList这种,在cpu密集型的应用中应该只使用传统for循环,在循环体执行时间比较长的应用中,传统for循环和迭代器循环的差别就很小了,这时候使用迭代器(for-each循环)也不会明显降低执行效率。

    参考:
    1、https://docs.oracle.com/javase/8/docs/technotes/guides/language/foreach.html
    2、Java虚拟机规范(Java SE 8)

    以上内容如有问题,烦请指出,谢谢!


    展开全文
  • parallel for指令和parallel指令是不同的。 parallel parallel指令只是指明后面的代码块被并行执行,对共享下标的访问次序无法控制和预知: #include&lt;stdio.h&gt; #include&lt;stdlib.h&...

    从parallel到parallel for

    parallel for指令和parallel指令是不同的。

    parallel

    parallel指令只是指明后面的代码块被并行执行,对共享下标的访问次序无法控制和预知:

    #include<stdio.h>
    #include<stdlib.h>
    #include<omp.h>
    
    int main(int argc,char *argv[])
    {
        int i;
        int thrdCnt=strtol(argv[1],NULL,10);
    #   pragma omp parallel num_threads(thrdCnt)
        for(i=0;i<6;i++)
            printf("我是%d\n",omp_get_thread_num());
        return 0;
    }

    多次输出

    [lzh@hostlzh OpenMP]$ ./test2.o 2
    我是0
    我是0
    我是0
    我是0
    我是0
    我是0
    我是1
    [lzh@hostlzh OpenMP]$ ./test2.o 2
    我是0
    我是0
    我是0
    我是0
    我是0
    我是1
    我是0
    [lzh@hostlzh OpenMP]$ ./test2.o 2
    我是0
    我是0
    我是0
    我是0
    我是0
    我是0
    我是1
    我是1
    我是1
    我是1
    我是1
    我是1
    [lzh@hostlzh OpenMP]$

    parallel for

    而parallel for指令能将合法的典型结构的for循环并行化,且在parallel for中循环变量的缺省作用域是私有的,线程之间不会相互影响:

    #include<stdio.h>
    #include<stdlib.h>
    #include<omp.h>
    
    int main(int argc,char *argv[])
    {
        int i;
        int thrdCnt=strtol(argv[1],NULL,10);
    #   pragma omp parallel for num_threads(thrdCnt)
        for(i=0;i<6;i++)
            printf("我是%d\n",omp_get_thread_num());
        return 0;
    }

    输出

    [lzh@hostlzh OpenMP]$ ./test2.o 2
    我是0
    我是0
    我是0
    我是1
    我是1
    我是1
    [lzh@hostlzh OpenMP]$

    可见对for循环进行了块划分。

    注意parallel for和parallel是完全不同的指令,parallel for指令后面直接跟随需要并行化的for,而不能像parallel那样修饰大括号扩起来的代码块。

    能够被parallel for正确并行化的for循环

    书上把这样的for循环称为典型的for循环。

    for循环头的允许形式

    fori=a;i<bi>bi<=bi>=b;i++++iiii+=ki=ki=i+ki=k+ii=ikfor(i=a;i<bi>bi<=bi>=b;i++++ii−−−−ii+=ki−=ki=i+ki=k+ii=i−k)

    对for循环体的限制

    循环次数必须由前面的for循环头本身确定,不能在循环体中做可能改变for循环次数判定的事情,如break和改变循环变量的值。

    书上的例子

    这个例子使用parallel for并行化求ππ值的一个for循环。

    用reduction归约子句保护了加入总和的过程,当然这里使用critical子句也可以,但保护的机制截然不同。

    用private子句为符号变量sign设定了私有作用域,以保证每个线程对这个变量都有自己的副本,防止在使用前又被其它线程赋值改变。

    用if-else判断解除了使用sign=-sign改变符号变量时具有的循环依赖问题,即本次循环中使用了前面循环计算得到的结果。循环依赖在循环并行化中往往是有害的,因为并行化的程序往往不按照串行的时间线执行,依赖了其它迭代次的数据是错误的,或者有时候所依赖的数据还没有计算出来(如斐波那契数列)。

    而对于循环变量i,因为在parallel for指令中,循环变量的缺省作用域是私有的,所以它已经受到保护。

    #include<stdio.h>
    #include<stdlib.h>
    #include<omp.h>
    
    int main(int argc,char *argv[])
    {
        double sum=0.0;//归约变量,被reduction子句保护
        int i;//循环变量,被parallel for保护为私有
        double sign;//表示符号的变量,被private子句保护
        int thrdCnt=strtol(argv[1],NULL,10);
    #   pragma omp parallel for \
        num_threads(thrdCnt) \
        reduction(+:sum) \
        private(sign)
        for(i=0;i<10000;i++)
        {
            //利用if-else判断对sign=-sign解除循环依赖
            if(i%2==0)
                sign=1.0;
            else
                sign=-1.0;
            //本质上是加到reduction子句为每个线程建立的私有变量上
            sum+=sign/(2*i+1);
        }
        printf("pi=%f\n",4*sum);//这个级数*4近似为pi
        return 0;
    }

    输出

    [lzh@hostlzh OpenMP]$ !gcc
    gcc -fopenmp -o test2.o test2.c
    [lzh@hostlzh OpenMP]$ ./test2.o 5
    pi=3.141493
    [lzh@hostlzh OpenMP]$

    私有作用域的变量在parallel和parallel for块刚开始时和结束后都是未指定的。

    展开全文
  • 【C++进阶】for循环以及for循环运行机制 for (expr1; expr2; expr3) statement 第一个表示式只在循环开始前执行一次并且无条件执行。 第二个表示式从第一次执行循环开始就在循环开始前运行做一次判断该表达式...
  • shell有三种循环for循环,while循环和until循环for命令 for循环命令用于根据项目清单确定的次数执行命令。例如,你可以根据文件或者用户清单执行相同的命令。for命令后面紧跟着用户自定义变量-关键字in,然后是...
  • vue自定义指令原理

    千次阅读 2020-05-05 20:03:07
    vue指令本质 指令本质上是装饰器,是vue对HTML元素的扩展,给HTML元素增加自定义功能,语义化HTML标签。vue编译DOM时,会执行与指令关联的JS代码,即找到指令对象,执行指令对象的相关方法。 自定义指令生命周期 ...
  • 理解 Python 的 for 循环

    万次阅读 多人点赞 2019-04-19 21:18:55
    在本篇博客中,我们将讨论 Python 中 for 循环原理。 我们将从一组基本例子和它的语法开始,还将讨论与 for 循环关联的 else 代码块的用处。 然后我们将介绍迭代对象、迭代器和迭代器协议,还会学习如何创建自己...
  • java增强型for循环和普通循环比较

    万次阅读 2016-09-30 15:17:44
     (1)对于非集合类(没有实现 Iterable接口)的数组遍历,增强型for循环和普通循环遍历原理相同,效率相同  (2)对于集合类(实现了Iterable接口),增强型for循环的遍历其本质就是迭代器 iterator的遍历,和普通循环遍历...
  • 指令系统4.1 指令系统的基本概念4.1.1 指令的基本格式1) 四地址指令2) 三地址指令3) 二地址指令4) 一地址指令5) 零地址指令4.1.2 定长操作码指令格式4.1.3 扩展操作码指令格式4.1.4 多字长指令格式4.1.5 指令格式的...
  • Java中foreach循环两种实现原理

    千次阅读 2018-06-22 22:11:08
    一、foreach循环Iterator模式 首先我们看一个foreach循环的main方法 import java.util.Collections; import java.util.List; import java.util.Map; public class Test { public static void main(String[] ...
  • 循环的软流水原理和实例展示

    千次阅读 2016-01-16 18:01:00
    一个超级简单有效的循环的软流水方法,让你可以清晰高效地写出软流水主体循环,装入代码和排空代码。 从此系统结构再也不用担心了。
  • 计算机指令与运算基础原理笔记

    千次阅读 2019-10-02 16:59:48
    一、汇编代码与指令 1.要让程序在一个 Linux 操作系统上跑起来,需要把整个程序翻译成汇编语言(ASM,Assembly Language)的程序,这个过程叫编译(Compile)成汇编代码。针对汇编代码,可以再用汇编器(Assembler...
  • 今天主要介绍那些基本的指令吧,具体软件的应用我也在熟悉中,还有如果有RSLogix5000(20.01版本)、RSLinx等软件不能授权的可以私密我,我把我的那个破解授权的东东给你啊。。。   一、位指令   XIC、XIO、OTE、...
  • 计算机组成原理(4.3)—— MIPS指令系统(RSIC)

    千次阅读 多人点赞 2020-08-16 22:03:24
    前文链接:计算机组成原理(4.1)—— 指令系统设计 文章目录一、MIPS架构基础1. 寄存器数据指定(1)MIPS架构中的寄存器安排(2)寄存器名称、编号和功能2. 存储器数据指定二、操作数类型和表示方式1. 操作数类型...
  • 目录 1.Nginx介绍 1.1 什么是Nginx? 1.2Nginx能做什么 1.3 为什么要选择用Nginx 2.Nginx的安装与配置 2.1 Nginx 安装 ...3.Nginx工作原理 3.1 工作原理: 3.1.1 Nginx处理Request请求过程解析 ...
  • 因为有if…else、for这样的条件和循环存在,这些指令也不会一路平直执行下去。 一个计算机程序是怎么被分解成一条条指令来执行的呢 1 CPU如何执行指令 CPU里差不多几百亿个晶体管 实际上,一条条计算机指令执行起来...
  • 浏览器工作原理详解

    万次阅读 多人点赞 2017-09-14 17:07:32
    学习浏览器的内部工作原理将有助于您作出更明智的决策,并理解那些最佳开发实践的个中缘由。 尽管这是一篇相当长的文档,但是我们建议您花些时间来仔细阅读;读完之后,您肯定会觉得所费不虚。  保罗·爱丽诗...
  • 上一讲,我们讲解了一行代码是怎么变成...对应的,CPU执行的也不只是一条指令,一般一个程序包含很多指令,因为有if…else、for这样的条件和循环存在,这些指令也不会一路平铺执行下去。 今天我们就在上一节的基础...
  • 在v-for循环中,通过自定义指令获取循环中每一项的值,然后进行处理,第一次进行v-for遍历,可以正常获取值,并渲染页面,但是list被重新赋值,第二次v-for遍历的时候,在页面中可以直接获取值,但是在指令中无法...
  • node.js基本工作原理及流程

    万次阅读 2016-12-31 12:07:54
    当一个指令到达主线程,主线程发现有I/O之后,直接把这个事件传给I/O线程,不会等待I/O结束后,再去处理下面的业务,而是拿到一个状态后立即往下走,这就是“单线程”、“异步I/O”。 I/O操作完之后呢?Node.js的...
  • MMU 工作原理

    万次阅读 2016-07-19 15:24:03
    一、内存管理单元MMU介绍 内存管理单元简称MMU,它负责虚拟地址到物理地址的映射,并提供硬件机制的内存访问权限检查。MMU使得每个用户进程拥有自己独立的地址空间,并通过内存访问权限的检查保护每个进程所用的...
  • assign (赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量 store (存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作 write (写入):...
  • 关于for循环的执行效率问题

    千次阅读 2017-12-09 20:18:57
     1 for(int i = 0;i  2 {  3 for(int j = 0;j  4 {  5 //balabala  6 }  7 }  8 和  9 for(int i = 0;i 10 { 11 for(int j = 0;j 12 { 13 //balabala
  • JVM及 GC 的 工作原理

    千次阅读 2018-07-11 15:20:36
    转载出处:https://blog.csdn.net/lzxadsl/article/details/50159939JVMJava 虚拟机 Java 虚拟机(Java virtual machine,...原理:编译后的 Java 程序指令并不直接在硬件系统的 CPU 上执行,而是由 JVM 执行。JVM...
  • 电脑键盘工作原理

    万次阅读 多人点赞 2016-11-24 08:34:46
    以下就键盘的构造及工作原理等问题进行介绍。 (一)键盘构造及工作原理 PS/2设备履行一种双向同步串行协议。换句话说,每次数据线上发送一位数据并且每在时钟线上发一个脉冲就被读入。设备可以发送...
  • c代码编译器工作原理

    千次阅读 2012-09-05 20:33:09
     对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。  后一种类型的优化同机器的硬件结构密切相关,最...
  • Tomcat工作原理详解

    万次阅读 多人点赞 2014-03-22 00:41:50
    Tomcat简介 ... OK - Session information for application at contextpath /examples Default maximum session inactive interval 30 minutes 5 、启动和关闭应用程序  在浏览器中输入 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 61,412
精华内容 24,564
关键字:

循环指令for的工作原理