• 用了那么久的 Java For 循环,你知道哪种方式效率最高吗?


    作为程序员每天除了写很多 if else 之外,写的最多的也包含 for 循环了,都知道我们 Java 中常用的 for 循环有两种方式,一种是使用 for loop,另一种是使用 foreach,那如果问你,这两种方式哪一种效率最高,你的回答是什么呢?今天阿粉就来带你看一下。

    首先我们先通过代码来实际测试一下,在计算耗时之前我们先创建一个大小集合,然后通过不断的获取集合中的内容来测试耗时。

    package com.example.demo;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * <br>
     * <b>Function:</b><br>
     * <b>Author:</b>@author ziyou<br>
     * <b>Date:</b>2022-06-26 12:22<br>
     * <b>Desc:</b>无<br>
     */
    public class ForTest {
      public static void main(String[] args) {
    
        //获取一个指定大小的 List 集合
        List<Integer> list = getList(1000000);
    
        // 开启 for loop 耗时计算
        long startFor = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
          Integer integer = list.get(i);
    
        }
        long costFor = System.currentTimeMillis() - startFor;
        System.out.println("for loop cost for ArrayList:" + costFor);
    
        // forEach 耗时计算
        long forEachStartTime = System.currentTimeMillis();
        for (Integer integer : list) {
        }
        long forEachCost = System.currentTimeMillis() - forEachStartTime;
        System.out.println("foreach cost for ArrayList:" + forEachCost);
    
      }
    
      public static List<Integer> getList(int size) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < size; i++) {
          list.add(i);
        }
        return list;
      }
    
    }

    简单说明一下上面的带,先创建一个 List ,然后通过两种方式的遍历来计算耗时,根据集合的大小不同,我们进行运行会得到下面的一些测试数据,不同人的机器上面运行的时间会不一定,不过差距应该也不会太大。

    size=

    10000

    100000

    1000000

    10000000

    for loop

    1

    2

    10

    12

    for each

    1

    3

    17

    34

    通过上面的测试结果我们可以发现,在集合相对较小的情况下,for loop 和 foreach 两者的耗时基本上没有什么差别,当集合的数据量相对较大的时候,可以明显看的出来,for loop 的效率要比 foreach 的效率高。

    至于为什么在大数据量的情况下 forEach 的效率要比 for 低,我们就要看下 forEach 的原理了。forEach 其实不是一种新的语法,而是一种 Java 的语法糖。在编译时,编译器会将这段代码转换成迭代器实现,并编译成字节码,我们可以再简单的看个 case,来实际看下字节码信息。

    我们再编写一个简单的类,代码如下:

    package com.example.demo;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * <br>
     * <b>Function:</b><br>
     * <b>Author:</b>@author ziyou<br>
     * <b>Date:</b>2022-06-26 13:06<br>
     * <b>Desc:</b>无<br>
     */
    public class ForEachTest {
      List<Integer> list;
    
      public void main(String[] args) {
        for (Integer integer : list) {
    
        }
      }
    
    
    }
    

    通过 javac ForEachTest.java 编译成 class 文件,再通过 javap -v ForEachTest 反编译,我们就会得到下面的字节码内容:

    Classfile /Users/silence/Downloads/demo/src/test/java/com/example/demo/ForEachTest.class
      Last modified 2022-6-26; size 643 bytes
      MD5 checksum 9cf01f7c8c87c2b4d62c39d437025b7f
      Compiled from "ForEachTest.java"
    public class com.example.demo.ForEachTest
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #8.#23         // java/lang/Object."<init>":()V
       #2 = Fieldref           #7.#24         // com/example/demo/ForEachTest.list:Ljava/util/List;
       #3 = InterfaceMethodref #25.#26        // java/util/List.iterator:()Ljava/util/Iterator;
       #4 = InterfaceMethodref #27.#28        // java/util/Iterator.hasNext:()Z
       #5 = InterfaceMethodref #27.#29        // java/util/Iterator.next:()Ljava/lang/Object;
       #6 = Class              #30            // java/lang/Integer
       #7 = Class              #31            // com/example/demo/ForEachTest
       #8 = Class              #32            // java/lang/Object
       #9 = Utf8               list
      #10 = Utf8               Ljava/util/List;
      #11 = Utf8               Signature
      #12 = Utf8               Ljava/util/List<Ljava/lang/Integer;>;
      #13 = Utf8               <init>
      #14 = Utf8               ()V
      #15 = Utf8               Code
      #16 = Utf8               LineNumberTable
      #17 = Utf8               main
      #18 = Utf8               ([Ljava/lang/String;)V
      #19 = Utf8               StackMapTable
      #20 = Class              #33            // java/util/Iterator
      #21 = Utf8               SourceFile
      #22 = Utf8               ForEachTest.java
      #23 = NameAndType        #13:#14        // "<init>":()V
      #24 = NameAndType        #9:#10         // list:Ljava/util/List;
      #25 = Class              #34            // java/util/List
      #26 = NameAndType        #35:#36        // iterator:()Ljava/util/Iterator;
      #27 = Class              #33            // java/util/Iterator
      #28 = NameAndType        #37:#38        // hasNext:()Z
      #29 = NameAndType        #39:#40        // next:()Ljava/lang/Object;
      #30 = Utf8               java/lang/Integer
      #31 = Utf8               com/example/demo/ForEachTest
      #32 = Utf8               java/lang/Object
      #33 = Utf8               java/util/Iterator
      #34 = Utf8               java/util/List
      #35 = Utf8               iterator
      #36 = Utf8               ()Ljava/util/Iterator;
      #37 = Utf8               hasNext
      #38 = Utf8               ()Z
      #39 = Utf8               next
      #40 = Utf8               ()Ljava/lang/Object;
    {
      java.util.List<java.lang.Integer> list;
        descriptor: Ljava/util/List;
        flags:
        Signature: #12                          // Ljava/util/List<Ljava/lang/Integer;>;
    
      public com.example.demo.ForEachTest();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 13: 0
    
      public void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=4, args_size=2
             0: aload_0
             1: getfield      #2                  // Field list:Ljava/util/List;
             4: invokeinterface #3,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
             9: astore_2
            10: aload_2
            11: invokeinterface #4,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
            16: ifeq          32
            19: aload_2
            20: invokeinterface #5,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
            25: checkcast     #6                  // class java/lang/Integer
            28: astore_3
            29: goto          10
            32: return
          LineNumberTable:
            line 17: 0
            line 19: 29
            line 20: 32
          StackMapTable: number_of_entries = 2
            frame_type = 252 /* append */
              offset_delta = 10
              locals = [ class java/util/Iterator ]
            frame_type = 250 /* chop */
              offset_delta = 21
    }
    SourceFile: "ForEachTest.java"

    反编译的内容很多,不一一解释,可以看到这个字节码的一般含义是使用 getfield 命令获取变量,并调用 List.iterator 获取迭代器实例再调用 iterator.hasNext,如果返回 true,则调用 iterator.next 方法,这是迭代器遍历集合的实现逻辑。

    写到这里有小伙伴就要问了,那以后遇到 List 集合我就用 for loop 了,不用 foreach了,毕竟前者的效率更好。那么接下来我们再看一个 case,这里我们把 ArrayList 换成 LinkedList,代码如下:

    public static List<Integer> getList(int size) {
        List<Integer> list = new LinkedList<>();
        for (int i = 0; i < size; i++) {
          list.add(i);
        }
        return list;
      }

    size=

    1000

    10000

    100000

    for loop

    27

    129

    7654

    For each

    2

    2

    15

    从上面的数据可以很明显的看到,当在处理 LinkedList 的时候,for loop 明显就慢很多了。相信具体的原因大家也知道,ArrayList 底层是基于数组结构的,所以使用 for loop 操作起来会很快,时间复杂度是 O(1),但是 LinkedList 底层是链表结构,此时如果在想通过索引来操作数据,时间复杂度将是 O (n*n)。

    所以具体使用哪种循环方式以及具体需要使用哪种数据结构,都需要根据实际的业务情况来选择,任何一种方案的存在都是合理的,你小伙你们认为呢?

  • 相关阅读:
    DTW学习(dynamic time warping)——思想、代码实现
    invokeBeanFactoryPostProcessors
    Spring Boot集成阿里云视频点播服务的过程记录
    编译原理复习——语法分析(自顶向下)
    【深度学习】- NLP系列文章之 1.文本表示以及mlp来处理分类问题
    将每个Excel文件的数据量统一减少至120000行
    什么是SAN网络?SAN网络上的存储备份和恢复怎么操作?SAN 测试网络存储与服务器架构架构优化
    Java——Spring面向切面编程(详解AOP和OOP的区别)
    Python蓝桥杯---数列求值
    Unity ECS最新DOTS环境搭建教程
  • 原文地址:https://blog.csdn.net/java_beautiful/article/details/125482723