ArrayList和LinkedList的几种循环遍历方式及性能对比分析

主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayList和LinkedList的源码实现分析性能结果,总结结论
通过本文你可以了解(1)List的五种遍历方式及各自性能 (2)foreach及Iterator的实现 (3)加深对ArrayList和LinkedList实现的了解。
阅读本文前希望你已经了解ArrayList顺序存储和LinkedList链式的结构,本文不对此进行介绍。

相关:HashMap循环遍历方式及其性能对比

1. List的五种遍历方式
下面只是简单介绍各种遍历示例(以ArrayList为例),各自优劣会在本文后面进行分析给出结论。
(1) for each循环

Java


1

2

3

4


List<Integer> list = new ArrayList<Integer>();

for (Integer j : list) {

// use j

}

(2) 显示调用集合迭代器

Java


1

2

3

4


List<Integer> list = new ArrayList<Integer>();

for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();) {

iterator.next();

}

Java


1

2

3

4

5


List<Integer> list = new ArrayList<Integer>();

Iterator<Integer> iterator = list.iterator();

while (iterator.hasNext()) {

iterator.next();

}

(3) 下标递增循环,终止条件为每次调用size()函数比较判断

Java


1

2

3

4


List<Integer> list = new ArrayList<Integer>();

for (int j = 0; j < list.size(); j++) {

list.get(j);

}

(4) 下标递增循环,终止条件为和等于size()的临时变量比较判断

Java


1

2

3

4

5


List<Integer> list = new ArrayList<Integer>();

int size = list.size();

for (int j = 0; j < size; j++) {

list.get(j);

}

(5) 下标递减循环

Java


1

2

3

4


List<Integer> list = new ArrayList<Integer>();

for (int j = list.size() - 1; j >= 0; j--) {

list.get(j);

}

在测试前大家可以根据对ArrayList和LinkedList数据结构及Iterator的了解,想想上面五种遍历方式哪个性能更优。

2、List五种遍历方式的性能测试及对比
以下是性能测试代码,会输出不同数量级大小的ArrayList和LinkedList各种遍历方式所花费的时间。

ArrayList和LinkedList循环性能对比测试代码

PS:如果运行报异常in thread “main” java.lang.OutOfMemoryError: Java heap space,请将main函数里面list size的大小减小。

其中getArrayLists函数会返回不同size的ArrayList,getLinkedLists函数会返回不同size的LinkedList。
loopListCompare函数会分别用上面的遍历方式1-5去遍历每一个list数组(包含不同大小list)中的list。
print开头函数为输出辅助函数。

测试环境为Windows7 32位系统 3.2G双核CPU 4G内存,Java 7,Eclipse -Xms512m -Xmx512m
最终测试结果如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29


compare loop performance of ArrayList

-----------------------------------------------------------------------

list size | 10,000 | 100,000 | 1,000,000 | 10,000,000

-----------------------------------------------------------------------

for each | 1 ms | 3 ms | 14 ms | 152 ms

-----------------------------------------------------------------------

for iterator | 0 ms | 1 ms | 12 ms | 114 ms

-----------------------------------------------------------------------

for list.size() | 1 ms | 1 ms | 13 ms | 128 ms

-----------------------------------------------------------------------

for size = list.size() | 0 ms | 0 ms | 6 ms | 62 ms

-----------------------------------------------------------------------

for j-- | 0 ms | 1 ms | 6 ms | 63 ms

-----------------------------------------------------------------------

compare loop performance of LinkedList

-----------------------------------------------------------------------

list size | 100 | 1,000 | 10,000 | 100,000

-----------------------------------------------------------------------

for each | 0 ms | 1 ms | 1 ms | 2 ms

-----------------------------------------------------------------------

for iterator | 0 ms | 0 ms | 0 ms | 2 ms

-----------------------------------------------------------------------

for list.size() | 0 ms | 1 ms | 73 ms | 7972 ms

-----------------------------------------------------------------------

for size = list.size() | 0 ms | 0 ms | 67 ms | 8216 ms

-----------------------------------------------------------------------

for j-- | 0 ms | 1 ms | 67 ms | 8277 ms

-----------------------------------------------------------------------

第一张表为ArrayList对比结果,第二张表为LinkedList对比结果。

表横向为同一遍历方式不同大小list遍历的时间消耗,纵向为同一list不同遍历方式遍历的时间消耗。
PS:由于首次遍历List会稍微多耗时一点,for each的结果稍微有点偏差,将测试代码中的几个Type顺序调换会发现,for each耗时和for iterator接近。

3、遍历方式性能测试结果分析
(1) foreach介绍
foreach是Java SE5.0引入的功能很强的循环结构,for (Integer j : list)应读作for each int in list。
for (Integer j : list)实现几乎等价于

Java


1

2

3

4


Iterator<Integer> iterator = list.iterator();

while(iterator.hasNext()) {

Integer j = iterator.next();

}

下面的分析会将foreach和显示调用集合迭代器两种遍历方式归类为Iterator方式,其他三种称为get方式遍历。

这时我们已经发现foreach的一大好处,简单一行实现了四行的功能,使得代码简洁美观,另一大好处是相对于下标循环而言的,foreach不必关心下标初始值和终止值及越界等,所以不易出错Effective-Java中推荐使用此种写法遍历,本文会验证这个说法。

使用foreach结构的类对象必须实现了Iterable接口,Java的Collection继承自此接口,List实现了Collection,这个接口仅包含一个函数,源码如下:

Java


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21


package java.lang;

import java.util.Iterator;

/**

* Implementing this interface allows an object to be the target of

* the "foreach" statement.

*

* @param <T> the type of elements returned by the iterator

*

* @since 1.5

*/

public interface Iterable<T> {

/**

* Returns an iterator over a set of elements of type T.

*

* @return an Iterator.

*/

Iterator<T> iterator();

}

iterator()用于返回一个Iterator,从foreach的等价实现中我们可以看到,会调用这个函数得到Iterator,再通过Iterator的next()得到下一个元素,hasNext()判断是否还有更多元素。Iterator源码如下:

Java


1

2

3

4

5

6

7


public interface Iterator<E> {

boolean hasNext();

E next();

void remove();

}

(2) ArrayList遍历方式结果分析


1

2

3

4

5

6

7

8

9

10

11

12

13

14


compare loop performance of ArrayList

-----------------------------------------------------------------------

list size | 10,000 | 100,000 | 1,000,000 | 10,000,000

-----------------------------------------------------------------------

for each | 1 ms | 3 ms | 14 ms | 152 ms

-----------------------------------------------------------------------

for iterator | 0 ms | 1 ms | 12 ms | 114 ms

-----------------------------------------------------------------------

for list.size() | 1 ms | 1 ms | 13 ms | 128 ms

-----------------------------------------------------------------------

for size = list.size() | 0 ms | 0 ms | 6 ms | 62 ms

-----------------------------------------------------------------------

for j-- | 0 ms | 1 ms | 6 ms | 63 ms

-----------------------------------------------------------------------

PS:由于首次遍历List会稍微多耗时一点,for each的结果稍微有点偏差,将测试代码中的几个Type顺序调换会发现,for each耗时和for iterator接近。

从上面我们可以看出:
a. 在ArrayList大小为十万之前,五种遍历方式时间消耗几乎一样
b. 在十万以后,第四、五种遍历方式快于前三种,get方式优于Iterator方式,并且

Java


1

2

3

4


int size = list.size();

for (int j = 0; j < size; j++) {

list.get(j);

}

用临时变量size取代list.size()性能更优。我们看看ArrayList中迭代器Iterator和get方法的实现

Java


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29


private class Itr implements Iterator<E> {

int cursor; // index of next element to return

int lastRet = -1; // index of last element returned; -1 if no such

int expectedModCount = modCount;

public boolean hasNext() {

return cursor != size;

}

@SuppressWarnings("unchecked")

public E next() {

checkForComodification();

int i = cursor;

if (i >= size)

throw new NoSuchElementException();

Object[] elementData = ArrayList.this.elementData;

if (i >= elementData.length)

throw new ConcurrentModificationException();

cursor = i + 1;

return (E) elementData[lastRet = i];

}

……

}

public E get(int index) {

rangeCheck(index);

return elementData(index);

}

从中可以看出get和Iterator的next函数同样通过直接定位数据获取元素,只是多了几个判断而已。

c . 从上可以看出即便在千万大小的ArrayList中,几种遍历方式相差也不过50ms左右,且在常用的十万左右时间几乎相等,考虑foreach的优点,我们大可选用foreach这种简便方式进行遍历。

(3) LinkedList遍历方式结果分析


1

2

3

4

5

6

7

8

9

10

11

12

13

14


compare loop performance of LinkedList

-----------------------------------------------------------------------

list size | 100 | 1,000 | 10,000 | 100,000

-----------------------------------------------------------------------

for each | 0 ms | 1 ms | 1 ms | 2 ms

-----------------------------------------------------------------------

for iterator | 0 ms | 0 ms | 0 ms | 2 ms

-----------------------------------------------------------------------

for list.size() | 0 ms | 1 ms | 73 ms | 7972 ms

-----------------------------------------------------------------------

for size = list.size() | 0 ms | 0 ms | 67 ms | 8216 ms

-----------------------------------------------------------------------

for j-- | 0 ms | 1 ms | 67 ms | 8277 ms

-----------------------------------------------------------------------

PS:由于首次遍历List会稍微多耗时一点,for each的结果稍微有点偏差,将测试代码中的几个Type顺序调换会发现,for each耗时和for iterator接近。

从上面我们可以看出:
a 在LinkedList大小接近一万时,get方式和Iterator方式就已经差了差不多两个数量级,十万时Iterator方式性能已经远胜于get方式。
我们看看LinkedList中迭代器和get方法的实现

Java


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52


private class ListItr implements ListIterator<E> {

private Node<E> lastReturned = null;

private Node<E> next;

private int nextIndex;

private int expectedModCount = modCount;

ListItr(int index) {

// assert isPositionIndex(index);

next = (index == size) ? null : node(index);

nextIndex = index;

}

public boolean hasNext() {

return nextIndex < size;

}

public E next() {

checkForComodification();

if (!hasNext())

throw new NoSuchElementException();

lastReturned = next;

next = next.next;

nextIndex++;

return lastReturned.item;

}

……

}

public E get(int index) {

checkElementIndex(index);

return node(index).item;

}

/**

* Returns the (non-null) Node at the specified element index.

*/

Node<E> node(int index) {

// assert isElementIndex(index);

if (index < (size >> 1)) {

Node<E> x = first;

for (int i = 0; i < index; i++)

x = x.next;

return x;

} else {

Node<E> x = last;

for (int i = size - 1; i > index; i--)

x = x.prev;

return x;

}

}

从上面代码中可以看出LinkedList迭代器的next函数只是通过next指针快速得到下一个元素并返回。而get方法会从头遍历直到index下标,查找一个元素时间复杂度为哦O(n),遍历的时间复杂度就达到了O(n2)。

所以对于LinkedList的遍历推荐使用foreach,避免使用get方式遍历。

(4) ArrayList和LinkedList遍历方式结果对比分析
从上面的数量级来看,同样是foreach循环遍历,ArrayList和LinkedList时间差不多,可将本例稍作修改加大list size会发现两者基本在一个数量级上。
但ArrayList get函数直接定位获取的方式时间复杂度为O(1),而LinkedList的get函数时间复杂度为O(n)。
再结合考虑空间消耗的话,建议首选ArrayList。对于个别插入删除非常多的可以使用LinkedList。

4、结论总结
通过上面的分析我们基本可以总结下:
(1) 无论ArrayList还是LinkedList,遍历建议使用foreach,尤其是数据量较大时LinkedList避免使用get遍历。
(2) List使用首选ArrayList。对于个别插入删除非常多的可以使用LinkedList。
(3) 可能在遍历List循环内部需要使用到下标,这时综合考虑下是使用foreach和自增count还是get方式。

时间: 2024-12-03 23:34:35

ArrayList和LinkedList的几种循环遍历方式及性能对比分析的相关文章

HashMap循环遍历方式及其性能对比

主要介绍HashMap的四种循环遍历方式,各种方式的性能测试对比,根据HashMap的源码实现分析性能结果,总结结论. 1. Map的四种遍历方式 下面只是简单介绍各种遍历示例(以HashMap为例),各自优劣会在本文后面进行分析给出结论. (1) for each map.entrySet() Java 1 2 3 4 5 Map<String, String> map = new HashMap<String, String>(); for (Entry<String,

C++中几种callable实现方式的性能对比

前言 C++中想实现一个callable的对象,通常有四种方式: std::function:最common的方式,一般会配合std::bind使用. function pointer:最C的方式,但没办法实现有状态的callable object. function object:就是重载了operator()的类,C++98的STL中经常用. lambda expression:不会污染namespace,一般来说编译器内部会实现为一个匿名的function object. 从原理上性能最好

详解JS几种变量交换方式以及性能分析对比_javascript技巧

前言 "两个变量之间的值得交换",这是一个经典的话题,现在也有了很多的成熟解决方案,本文主要是列举几种常用的方案,进行大量计算并分析对比. 起由 最近做某个项目时,其中有一个需求是交换数组中的两个元素.当时使用的方法是: arr = [item0,item1,...,itemN]; //最初使用这段代码来交换第0个和第K(k<N)个元素 arr[0] = arr.splice(k, 1, arr[0])[0]; 当时觉得这种方法很优雅,高逼格... 后来,业余时间又拿这个研究下了

Java基础-16总结List的子类,ArrayList,Vector,LinkedList,泛型,增强for循环,静态导入,可变参数

你需要的是什么,直接评论留言. 获取更多资源加微信公众号"Java帮帮" (是公众号,不是微信好友哦) 还有"Java帮帮"今日头条号,技术文章与新闻,每日更新,欢迎阅读 学习交流请加Java帮帮交流QQ群553841695 分享是一种美德,分享更快乐! 1:List的子类(掌握) (1)List的子类特点 ArrayList: 底层数据结构是数组,查询快,增删慢 线程不安全,效率高 Vector: 底层数据结构是数组,查询快,增删慢 线程安全,效率低 Linked

3种LVS/Nginx/HAProxy负载均衡器的对比分析

现在网站发展的趋势对网络负载均衡的使用是随着网站规模的提升根据不同的阶段来使用不同的技术: 一种是通过硬件来进行进行,常见的硬件有比较昂贵的NetScaler.F5.Radware和Array等商用的负载均衡器,它的优点就是有专业的维护团队来对这些服务进行维护.缺点就是花销太大,所以对于规模较小的网络服务来说暂时还没有需要使用:另外一种就是类似于LVS/HAProxy.Nginx的基于Linux的开源免费的负载均衡软件策略,这些都是通过软件级别来实现,所以费用非常低廉,所以我个也比较推荐大家采用

java中map的循环遍历和map的获取值的办法

map的循环遍历方式   代码如下 复制代码 package com.sec.map;     import java.util.HashMap; import java.util.Iterator; import java.util.Map;     public class TestMap {         public static void main(String[] args) {                   Map<String, String> map = new Ha

Alluxio源码分析定位策略:循环遍历策略RoundRobinPolicy

        循环遍历策略RoundRobinPolicy是一种通过循环遍历方式并且跳过没有足够空间workers的为下一个数据块选择worker的策略.如果没有worker被找到,该策略会返回null.在RoundRobinPolicy内部,有三个十分重要的成员变量,如下: // 初始化的BlockWorkerInfo列表,每次都从这个列表中选择BlockWorkerInfo private List<BlockWorkerInfo> mWorkerInfoList; // mWorker

Java中ArrayList和LinkedList的遍历与性能分析_java

前言 通过本文你可以了解List的五种遍历方式及各自性能和foreach及Iterator的实现,加深对ArrayList和LinkedList实现的了解.下面来一起看看吧. 一.List的五种遍历方式 1.for each循环 List<Integer> list = new ArrayList<Integer>(); for (Integer j : list) { // use j } 2.显示调用集合迭代器 List<Integer> list = new Ar

PHP循环遍历数组的3种方法list()、each()和while总结_php实例

①each()函数 each()函数需要传递一个数组作为参数,返回数组中当前元素的键/值对,并向后移动数组指针到下一个元素的位置.键/值对被返回带有4个元素的关联和索引混合的数组,键名分别为0.1.key和value.其中键名0和key对应的值是一样的,是数组元素的键名,1和value则包含有数组元素的值.如果内部指针越过了数组的末端,则each()返回FALSE.each()函数的使用如下所示: 复制代码 代码如下: <?php $contact = array("ID" =&