Java中几种遍历List方式的性能比较

java中遍历List有以下几种方式:

  1. jdk8中新增的lambda遍历 (list.forEach)
  2. jdk5中的forEach
  3. 按下标遍历
  4. iterator迭代器

毫无疑问,最方便的方式就是采用lambda方式遍历,代码量最少,可是其性能如何呢?看下面实验。

硬件环境:MacBook Pro (Retina, 15-inch, Mid 2015) –

2.2 GHz Intel Core i7、

16 GB 1600 MHz DDR3

JDK版本:jdk1.8.0_91

代码如下

public static void main(String[] args) {
    List<Integer> in = IntStream.range(0, 1000000).boxed().collect(Collectors.toList());
    int times = 10000;

    //lambda:43124(ms)
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < times; i++) {
      lambda(in);
    }
    System.out.println("lambda:" + (System.currentTimeMillis() - startTime));


    //foreach:29442
    startTime = System.currentTimeMillis();

    for (int i = 0; i < times; i++) {
      foreach(in);
    }
    System.out.println("foreach:" + (System.currentTimeMillis() - startTime));

    //for1:17517
    startTime = System.currentTimeMillis();

    for (int i = 0; i < times; i++) {
      for1(in);
    }
    System.out.println("for1:" + (System.currentTimeMillis() - startTime));

    //for2:18432
    startTime = System.currentTimeMillis();

    for (int i = 0; i < times; i++) {
      for2(in);
    }
    System.out.println("for2:" + (System https://ed-oesterreichische.at/viagra-generika/.currentTimeMillis() - startTime));


    //iterator:30997
    startTime = System.currentTimeMillis();

    for (int i = 0; i < times; i++) {
      iterator(in);
    }
    System.out.println("iterator:" + (System.currentTimeMillis() - startTime));
  }

  private static void foreach(List<Integer> in) {
    int count[] = new int[]{0};
    for (int a : in) {
      if (a % 2 == 0) {
        count[0] = count[0] + 1;
      }
    }

  }

  private static void lambda(List<Integer> in) {
    int count[] = new int[]{0};
    in.forEach(a -> {
      if (a % 2 == 0) {
        count[0] = count[0] + 1;
      }
    });

  }


  private static void for1(List<Integer> in) {
    int count[] = new int[]{0};
    for (int i = 0, j = in.size(); i < j; i++) {
      if (in.get(i) % 2 == 0) {
        count[0] = count[0] + 1;
      }
    }


  }


  private static void for2(List<Integer> in) {
    int count[] = new int[]{0};
    for (int i = 0; i < in.size(); i++) {
      if (in.get(i) % 2 == 0) {
        count[0] = count[0] + 1;
      }
    }

  }


  private static void iterator(List<Integer> in) {
    int count[] = new int[]{0};
    Iterator<Integer> it=in.iterator();
    while(it.hasNext()){
      if (it.next() % 2 == 0) {
        count[0] = count[0] + 1;
      }
    }

  }

对size为100万的list遍历1万次耗时对比,单位毫秒

由此图可以看出

  1. 耗时最短是传统的按下标循环遍历的模式,其次是foreach、iterator方式。耗时最长的是lambda方式。
  2. 循环调用size()方法会有一定的性能损失,可以将size赋给另一个变量

关于ArrayList遍历性能,在api文档中找到如下解释

public interface RandomAccess
Marker interface used by List implementations to indicate that they support fast (generally constant time) random access. The primary purpose of this interface is to allow generic algorithms to alter their behavior to provide good performance when applied to either random or sequential access lists.
The best algorithms for manipulating random access lists (such as ArrayList) can produce quadratic behavior when applied to sequential access lists (such as LinkedList). Generic list algorithms are encouraged to check whether the given list is an instanceof this interface before applying an algorithm that would provide poor performance if it were applied to a sequential access list, and to alter their behavior if necessary to guarantee acceptable performance.
It is recognized that the distinction between random and sequential access is often fuzzy. For example, some List implementations provide asymptotically linear access times if they get huge, but constant access times in practice. Such a List implementation should generally implement this interface. As a rule of thumb, a List implementation should implement this interface if, for typical instances of the class, this loop:
       for (int i=0, n=list.size(); i < n; i++)
           list.get(i);
   
runs faster than this loop:
       for (Iterator i=list.iterator(); i.hasNext(); )
           i.next();

也就是按下标遍历的方式的性能要由于迭代的方式。foreach循环的底层实现原理就是迭代器Iterator,参见Java语法糖1:可变长度参数以及foreach循环原理。所以后半句”反过来,如果是顺序访问的,则使用Iterator会效率更高”的意思就是顺序访问的那些类实例,使用foreach循环去遍历。

此条目发表在技术分类目录。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。