作为程序员每天除了写很多 if else 之外,用那写的循效率最多的也包含 for 循环了,都知道我们 Java 中常用的环知 for 循环有两种方式,一种是道种使用 for loop,另一种是最高使用 foreach,那如果问你,用那这两种方式哪一种效率最高,循效率你的环知回答是什么呢?今天阿粉就来带你看一下。 首先我们先通过代码来实际测试一下,道种在计算耗时之前我们先创建一个大小集合,最高然后通过不断的用那获取集合中的内容来测试耗时。 package com.example.demo; import java.util.ArrayList; import java.util.List; / * * Function: * Author:@author ziyou * Date:2022-06-26 12:22 * Desc:无 */ public class ForTest { public static void main(String[] args) { //获取一个指定大小的循效率 List 集合 List // 开启 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 List 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,来实际看下字节码信息。 我们再编写一个简单的类,云南idc服务商代码如下: package com.example.demo; import java.util.ArrayList; import java.util.List; / * * Function: * Author:@author ziyou * Date:2022-06-26 13:06 * Desc:无 */ public class ForEachTest { 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." #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 #13 = Utf8 #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 // " #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 descriptor: Ljava/util/List; flags: Signature: #12 // Ljava/util/List 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." 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 } 反编译的内容很多,不一一解释,可以看到这个字节码的一般含义是使用 getfield 命令获取变量,并调用 List.iterator 获取迭代器实例再调用 iterator.hasNext,如果返回 true,则调用 iterator.next 方法,这是迭代器遍历集合的实现逻辑。 写到这里有小伙伴就要问了,那以后遇到 List 集合我就用 for loop 了,不用 foreach了,毕竟前者的效率更好。那么接下来我们再看一个 case,这里我们把 ArrayList 换成 LinkedList,代码如下: public static List List 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)。 所以具体使用哪种循环方式以及具体需要使用哪种数据结构,都需要根据实际的业务情况来选择,任何一种方案的存在都是合理的,你小伙你们认为呢?