Java遍历List的方法主要有:

for each

for(bject o :list)
   {
   }


Iterator

Iterator iter = list.iterator();
   while(iter.hasNext()){
      Object o = iter.next();
   }

 loop without size

int size = list.size();
   for(inti=0;i<size;i++){
      Object o= list.get(i);
   }

 loop with size

for(inti=0;i<list.size();i++){
      Object o= list.get(i);
   }

 

注:这里我们不比较while和for的形式,这对效率影响几乎是可以忽略的。

 

我们是否能简单的得出结论,哪个更快,哪个更慢呢?

严谨一点的方法是:基于实验与数据,才能作出判断。

ArrayList测试分析,经过编写测试代码,


结果如下:(时间单位:纳秒)

Size

10

100

1,000

10,000

100,000

1,000,000

ForEach

448,319

558,757

732,009

2,074,092

6,169,315

15,347,540

IteratorWay

22,169

54,603

86,215

513,186

4,786,587

14,032,553

WithoutSize

14,369

32,023

158,472

828,897

3,685,905

9,457,398

WithSize

29,149

47,213

91,963

557,936

5,148,280

10,051,462

 

可以看出,直接用循环的方法,get(index)来获取对象,是最快的方式。而且把i<list.size()放到循环中去判断,会影响效率。

For Each的效率最差,用迭代器的效率也没有很好。但只是相对而言,其实从时间上看最多也就差几毫秒。

 

然而,这并不是事实的全部真相!!!

 

上面的测试,我们只是用了ArrayList来做为List的实现类。所以才有上面的结论。

For each其实也是用了迭代器来实现,只不过这个Iterator是Java编译器帮我们生成的,所以我们不需要再手动去编写。但是因为每次都要做类型转换检查,所以花费的时间比Iterator略长。时间复杂度和Iterator一样,也因为用了迭代器,所以速度上受了影响。不如直接get(index)快。


那为何get(index)会比较快呢?


因为ArrayList是通过动态数组来实现的,支持随机访问,所以get(index)是很快的。迭代器,其实也是通过数组名+下标来获取,而且增加了逻辑,自然会比get(index)慢。

看ArrayList的迭代器的源代码就清楚了:

 

public boolean hasNext()
    {
       returncursor != size;
    }
 
    public Object next()
    {
       checkForComodification();
       int i = cursor;
       if(i >=size)
          throw new NoSuchElementException();
       Object aobj[] = elementData;
       if(i >=aobj.length)
       {
          throw new ConcurrentModificationException();
       } else
       {
           cursor = i + 1;
           returnaobj[lastRet =i];
       }
   }

 

LinkedList测试分析

接下来,我们用LinkedList试试,看看会产生什么效果:(时间单位:纳秒)

Size

10

100

1,000

10,000

100,000

1,000,000

ForEach

542,745

388,379

952,063

2,257,196

9,426,607

12,141,976

IteratorWay

25,454

62,814

110,848

753,767

5,875,361

12,141,976

WithoutSize

27,096

95,248

3,343,097

51,302,568

3,720,958,713

692,276,304,569

WithSize

13,138

98,531

2,137,726

40,157,815

3,671,762,259

668,285,601,444

 

结果确实不简单,跟ArrayList完全不一样了。最突出的就是get(index)的方式,随着size的增加,急剧上升。到10万数据量时,光遍历时间都要三四秒,这是很可怕的。那为何会有这样的结果呢?还是和LinkedList的实现方式有关。LinkedList是通过双向链表实现的,无法支持随机访问。当你要向一个链表取第index个元素时,它需要二分后从某一端开始找,一个一个地数才能找到该元素。这样一想,就能明白为何get(index)如此费时了。

public Object get(inti)
    {
        checkElementIndex(i);
        return node(i).item;
}
 
Node node(inti)
    {
        if(i <size >> 1)
        {
            Node node1 = first;
            for(intj = 0; j < i; j++)
                node1 = node1.next;
 
            returnnode1;
        }
        Node node2 = last;
        for(intk = size - 1;k > i; k--)
            node2 = node2.prev;
 
        returnnode2;
    }

 

而迭代器提供的是获取下一个的方法,时间复杂度为O(1),所以会比较快。

 

public boolean hasNext()
{
    return nextIndex < size;
}
 
public Object next()
{
    checkForComodification();
    if(!hasNext())
    {
        throw new NoSuchElementException();
    } else
    {
        lastReturned = next;
        next = next.next;
        nextIndex++;
        returnlastReturned.item;
    }
}

 

看这迭代器的源代码还是很理解的。

 

总结


(1)对于ArrayList和LinkedList,在size小于1000时,每种方式的差距都在几ms之间,差别不大,选择哪个方式都可以。

(2)对于ArrayList,无论size是多大,差距都不大,选择哪个方式都可以。

(3)对于LinkedList,当size较大时,建议使用迭代器或for-each的方式进行遍历,否则效率会有较明显的差距。

 

所以,综合来看,建议使用for-each,代码简洁,性能也不差。

另外,当效率不是重点时,应该在设计上花更多心思了。实际上,把大量对象放到List里面去,本身就应该是要考虑的问题。


关于性能测试跟硬件也有关系,仅供参考