作为程序员每天除了写很多 if else
之外,写的最多的也包含 for
循环了,都知道我们 Java
中常用的 for
循环有两种方式,一种是使用 for loop
,另一种是使用 foreach
,那如果问你,这两种方式哪一种效率最高,你的回答是什么呢?
首先我们先通过代码来实际测试一下,在计算耗时之前我们先创建一个大小集合,然后通过不断的获取集合中的内容来测试耗时。
package com.example.demo;import java.util.ArrayList;
import java.util.List;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.classLast modified 2022-6-26; size 643 bytesMD5 checksum 9cf01f7c8c87c2b4d62c39d437025b7fCompiled from "ForEachTest.java"
public class com.example.demo.ForEachTestminor version: 0major version: 52flags: 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: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 13: 0public void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLICCode:stack=1, locals=4, args_size=20: aload_01: getfield #2 // Field list:Ljava/util/List;4: invokeinterface #3, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;9: astore_210: aload_211: invokeinterface #4, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z16: ifeq 3219: aload_220: invokeinterface #5, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;25: checkcast #6 // class java/lang/Integer28: astore_329: goto 1032: returnLineNumberTable:line 17: 0line 19: 29line 20: 32StackMapTable: number_of_entries = 2frame_type = 252 /* append */offset_delta = 10locals = [ 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)
。
所以具体使用哪种循环方式以及具体需要使用哪种数据结构,都需要根据实际的业务情况来选择,任何一种方案的存在都是合理的,你小伙你们认为呢?欢迎在评论区留言讨论。
往期推荐
如何优雅的写 Controller 层代码?
一个依赖搞定Spring Boot 配置文件脱敏
面试突击58:truncate、delete和drop的6大区别!