JAVA中for循环和for each哪个效率更高

86 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

前言

Java有两种遍历集合的方法。一个是最基本的for循环,另一个是jdk5引入的for-each。使用这种方法,我们可以更方便地遍历数组和集合。但你有没有想过这两种方法哪一个在遍历集合时效率更高?

for-each实现原理

for-each不是一种新的语法,而是Java的语法糖。在编译时,编译器将此代码转换为迭代器实现并将其编译为字节码。我们可以通过执行命令 javap -verbose TestForeach 来反编译以下已编译的代码。

public class TestForeach {
    List<Integer> integers;
    public void testForeach(){
        for(Integer i : integers){

        }
    }
}

执行命令 javap -verbose TestForeach得到字节码如下:

public void testForeach();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=3, args_size=1
         0: aload_0
         1: getfield      #2                  // Field integers:Ljava/util/List;
         4: invokeinterface #3,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
         9: astore_1
        10: aload_1
        11: invokeinterface #4,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
        16: ifeq          32
        19: aload_1
        20: invokeinterface #5,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
        25: checkcast     #6                  // class java/lang/Integer
        28: astore_2
        29: goto          10
        32: return
      LineNumberTable:
        line 11: 0
        line 13: 29
        line 14: 32
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           29       0     2     i   Ljava/lang/Integer;
            0      33     0  this   Ltest/TestForeach;
}

此字节码的含义是使用getfield命令获取变量integers并调用List.iterator获取迭代器实例, 然后调用iterator.hasNext()。如果返回true,调用iterator.next方法。这就是迭代器遍历集合的实现逻辑。

测试比较

现在让我们使用for循环方法和for-each方法进行基准测试。

public class ForLoopTest {

    public static void main(String[] args) {
        List<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < 10000000; i++) {
            arrayList.add(i);
        }

        long arrayListStartTime = System.currentTimeMillis();
        for (int i = 0; i < arrayList.size(); i++) {
            arrayList.get(i);
        }

        long arrayListCost =System.currentTimeMillis()-arrayListStartTime;
        System.out.println("ArrayList for loop traversal cost: "+ arrayListCost);

        long arrayListForeachStartTime = System.currentTimeMillis();
        for (Integer integer : arrayList) {
 						arrayList.get(integer);
        }

        long arrayListForeachCost =System.currentTimeMillis()-arrayListForeachStartTime;
        System.out.println("ArrayList foreach traversal cost: "+ arrayListForeachCost);
}

测试结果如下:

size=10000size=100000size=1000000
for loop3ms4ms6ms
foreach4ms7ms13ms

如您所见,结果是显而易见的。对于ArrayList,使用For循环方法的性能优于for-each方法。我们可以宣布for循环获胜吗?答案是否定的。在下一个基准测试中,我们将 ArrayList 改为 LinkedList

测试结果如下:

size=10000size=100000size=1000000
for loop65ms5837ms84453ms
foreach1ms6ms13ms

原因分析

有些初学者可能想知道为什么使用for循环方法ArrayList遍历更快,而LinkedList则更慢,非常慢?这是由ArrayList和LinkedList数据结构决定的。

  • ArrayList 底层使用数组来存储元素。数组是连续的内存空间。数据可以通过索引来获取。时间复杂度为O(1),速度较快。
  • LinkedList 的底层是一个双向链表。使用for循环来实现遍历,每次都需要从链表的头节点开始。时间复杂度为O (n*n)

总结

  • 当使用ArrayList时,for循环方法更快,因为for-each是由迭代器实现的,它需要执行并发修改验证。

  • 当使用LinkedList时,for-each比for循环快得多,因为LinkedList是通过使用双向链表实现的。每个寻址都需要从头节点开始。如果我们需要遍历LinkedList,我们需要避免使用for循环。

  • 使用迭代器模式,for-each不需要关心集合的特定实现。如果需要替换集合,则无需修改代码即可轻松替换。