Java中ArrayList和LinkedList的遍历与性能分析
前言
通过本文你可以了解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 arraylist<integer>(); for (iterator<integer> iterator = list.iterator(); iterator.hasnext();) { iterator.next(); }
或
list<integer> list = new arraylist<integer>(); iterator<integer> iterator = list.iterator(); while (iterator.hasnext()) { iterator.next(); }
3、下标递增循环,终止条件为每次调用size()函数比较判断
list<integer> list = new arraylist<integer>(); for (int j = 0; j < list.size(); j++) { list.get(j); }
4、下标递增循环,终止条件为和等于size()的临时变量比较判断
list<integer> list = new arraylist<integer>(); int size = list.size(); for (int j = 0; j < size; j++) { list.get(j); }
5、下标递减循环
list<integer> list = new arraylist<integer>(); for (int j = list.size() - 1; j >= 0; j--) { list.get(j); }
list五种遍历方式的性能测试及对比
以下是性能测试代码,会输出不同数量级大小的arraylist和linkedlist各种遍历方式所花费的时间。
package cn.trinea.java.test; import java.text.decimalformat; import java.util.arraylist; import java.util.calendar; import java.util.iterator; import java.util.linkedlist; import java.util.list; /** * javalooptest * * @author www.trinea.cn 2013-10-28 */ public class javalooptest { public static void main(string[] args) { system.out.print("compare loop performance of arraylist"); looplistcompare(getarraylists(10000, 100000, 1000000, 9000000)); system.out.print("\r\n\r\ncompare loop performance of linkedlist"); looplistcompare(getlinkedlists(100, 1000, 10000, 100000)); } public static list<integer>[] getarraylists(int... sizearray) { list<integer>[] listarray = new arraylist[sizearray.length]; for (int i = 0; i < listarray.length; i++) { int size = sizearray[i]; list<integer> list = new arraylist<integer>(); for (int j = 0; j < size; j++) { list.add(j); } listarray[i] = list; } return listarray; } public static list<integer>[] getlinkedlists(int... sizearray) { list<integer>[] listarray = new linkedlist[sizearray.length]; for (int i = 0; i < listarray.length; i++) { int size = sizearray[i]; list<integer> list = new linkedlist<integer>(); for (int j = 0; j < size; j++) { list.add(j); } listarray[i] = list; } return listarray; } public static void looplistcompare(list<integer>... listarray) { printheader(listarray); long starttime, endtime; // type 1 for (int i = 0; i < listarray.length; i++) { list<integer> list = listarray[i]; starttime = calendar.getinstance().gettimeinmillis(); for (integer j : list) { // use j } endtime = calendar.getinstance().gettimeinmillis(); printcosttime(i, listarray.length, "for each", endtime - starttime); } // type 2 for (int i = 0; i < listarray.length; i++) { list<integer> list = listarray[i]; starttime = calendar.getinstance().gettimeinmillis(); // iterator<integer> iterator = list.iterator(); // while(iterator.hasnext()) { // iterator.next(); // } for (iterator<integer> iterator = list.iterator(); iterator.hasnext();) { iterator.next(); } endtime = calendar.getinstance().gettimeinmillis(); printcosttime(i, listarray.length, "for iterator", endtime - starttime); } // type 3 for (int i = 0; i < listarray.length; i++) { list<integer> list = listarray[i]; starttime = calendar.getinstance().gettimeinmillis(); for (int j = 0; j < list.size(); j++) { list.get(j); } endtime = calendar.getinstance().gettimeinmillis(); printcosttime(i, listarray.length, "for list.size()", endtime - starttime); } // type 4 for (int i = 0; i < listarray.length; i++) { list<integer> list = listarray[i]; starttime = calendar.getinstance().gettimeinmillis(); int size = list.size(); for (int j = 0; j < size; j++) { list.get(j); } endtime = calendar.getinstance().gettimeinmillis(); printcosttime(i, listarray.length, "for size = list.size()", endtime - starttime); } // type 5 for (int i = 0; i < listarray.length; i++) { list<integer> list = listarray[i]; starttime = calendar.getinstance().gettimeinmillis(); for (int j = list.size() - 1; j >= 0; j--) { list.get(j); } endtime = calendar.getinstance().gettimeinmillis(); printcosttime(i, listarray.length, "for j--", endtime - starttime); } } static int first_column_length = 23, other_column_length = 12, total_column_length = 71; static final decimalformat comma_format = new decimalformat("#,###"); public static void printheader(list<integer>... listarray) { printrowdivider(); for (int i = 0; i < listarray.length; i++) { if (i == 0) { stringbuilder sb = new stringbuilder().append("list size"); while (sb.length() < first_column_length) { sb.append(" "); } system.out.print(sb); } stringbuilder sb = new stringbuilder().append("| ").append(comma_format.format(listarray[i].size())); while (sb.length() < other_column_length) { sb.append(" "); } system.out.print(sb); } total_column_length = first_column_length + other_column_length * listarray.length; printrowdivider(); } public static void printrowdivider() { system.out.println(); stringbuilder sb = new stringbuilder(); while (sb.length() < total_column_length) { sb.append("-"); } system.out.println(sb); } public static void printcosttime(int i, int size, string casename, long costtime) { if (i == 0) { stringbuilder sb = new stringbuilder().append(casename); while (sb.length() < first_column_length) { sb.append(" "); } system.out.print(sb); } stringbuilder sb = new stringbuilder().append("| ").append(costtime).append(" ms"); while (sb.length() < other_column_length) { sb.append(" "); } system.out.print(sb); if (i == size - 1) { printrowdivider(); } } }
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
最终测试结果如下:
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
接近。
遍历方式性能测试结果分析
1、foreach介绍
foreach是java se5.0引入的功能很强的循环结构,for (integer j : list)
应读作for each int in list
。
for (integer j : list)
实现几乎等价于
iterator<integer> iterator = list.iterator(); while(iterator.hasnext()) { integer j = iterator.next(); }
foreach代码书写简单,不必关心下标初始值和终止值及越界等,所以不易出错
2、arraylist遍历方式结果分析
a. 在arraylist大小为十万之前,五种遍历方式时间消耗几乎一样
b. 在十万以后,第四、五种遍历方式快于前三种,get方式优于iterator方式,并且
int size = list.size(); for (int j = 0; j < size; j++) { list.get(j); }
用临时变量size取代list.size()
性能更优。我们看看arraylist中迭代器iterator
和get
方法的实现
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遍历方式结果分析
a. 在linkedlist大小接近一万时,get
方式和iterator
方式就已经差了差不多两个数量级,十万时iterator
方式性能已经远胜于get方式。
我们看看linkedlist中迭代器和get
方法的实现
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。
结论总结
通过上面的分析我们基本可以总结下:
- 无论arraylist还是linkedlist,遍历建议使用foreach,尤其是数据量较大时linkedlist避免使用get遍历。
- list使用首选arraylist。对于个别插入删除非常多的可以使用linkedlist。
- 可能在遍历list循环内部需要使用到下标,这时综合考虑下是使用foreach和自增count还是get方式。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家学习或者使用java的时候能有所帮助,如果有疑问大家可以留言交流。
推荐阅读
-
Java中ArrayList和LinkedList的遍历与性能分析
-
Java中ArrayList和LinkedList之间的区别_动力节点Java学院整理
-
Java中ArrayList和LinkedList之间的区别_动力节点Java学院整理
-
JAVA LinkedList和ArrayList的使用及性能分析
-
JAVA LinkedList和ArrayList的使用及性能分析
-
系统设计和开发中,方法论比技术更重要--兼谈怎样做Java服务器的性能分析和调整
-
分析Java中Map的遍历性能问题
-
java中ArrayList和LinkedList的区别详解
-
java中HashMap的7种遍历方式与性能分析
-
java中对比ArrayList与LinkedList的图文详情