List的实现类主要是ArrayList和LinkedList,两个主要的差别是ArrayList是通过数组实现的,但LinkedList是通过链表实现。
可以想象,ArrayList在随机访问效率上远高于LinkedList,因为LinkedList访问一个元素必须从头节点开始依次访问知道找到目标节点,所以时间复杂度为O(n),而ArrayList的随机访问复杂度几乎是O(1)。
但频繁进行插入删除数据的时候,LinkedList的效率远高于ArrayList,因为LinedList只需要改变指针(引用)的内容,时间复杂度为O(1),而ArrayList添加与删除需要大量移动元素,时间复杂度为O(n)。
遍历效率分析
public class ListTest {
List<Integer> array = new ArrayList<Integer>();
List<Integer> link = new LinkedList<Integer>();
private final int size;
private long start, end;
public ListTest(int size) {
this.size = size;
for (int i = 0; i < size; i++) {
int t = (int) (Math.random() * 1000);
array.add(t);
link.add(t);
}
}
public void ArrayIteratorTraversal() {
Iterator<Integer> i = array.iterator();
int t;
start = System.currentTimeMillis();
while (i.hasNext()) {
t = i.next();
}
end = System.currentTimeMillis();
System.out.println("ArrayList使用Iterator遍历" + size + "个数据花了"
+ (end - start) + "ms");
}
public void LinkIteratorTraversal() {
Iterator<Integer> i = link.iterator();
int t;
start = System.currentTimeMillis();
while (i.hasNext()) {
t = i.next();
}
end = System.currentTimeMillis();
System.out.println("LinkedList使用Iterator遍历" + size + "个数据花了"
+ (end - start) + "ms");
}
public void ArrayListIteratorTraversal() {
ListIterator<Integer> i = array.listIterator();
int t;
start = System.currentTimeMillis();
while (i.hasNext()) {
t = i.next();
}
end = System.currentTimeMillis();
System.out.println("ArrayList使用ListIterator遍历" + size + "个数据花了"
+ (end - start) + "ms");
}
public void LinkListIteratorTraversal() {
ListIterator<Integer> i = link.listIterator();
int t;
start = System.currentTimeMillis();
while (i.hasNext()) {
t = i.next();
}
end = System.currentTimeMillis();
System.out.println("LinkedList使用ListIterator遍历" + size + "个数据花了"
+ (end - start) + "ms");
}
public void ArrayCycleTraversal() {
int t;
start = System.currentTimeMillis();
for (int i = 0; i < size; i++)
t = array.get(i);
end = System.currentTimeMillis();
System.out.println("ArrayList使用循环遍历" + size + "个数据花了"
+ (end - start) + "ms");
}
public void LinkCycleTraversal() {
int t;
start = System.currentTimeMillis();
for (int i = 0; i < size; i++)
t = link.get(i);
end = System.currentTimeMillis();
System.out.println("LinkedList使用循环遍历" + size + "个数据花了"
+ (end - start) + "ms");
}
public static void main(String[] args) {
ListTest t = new ListTest(10000000);
System.out.println("*******开始*******");
t.ArrayIteratorTraversal();
t.LinkIteratorTraversal();
t.ArrayListIteratorTraversal();
t.LinkListIteratorTraversal();
t.ArrayCycleTraversal();
// t.LinkCycleTraversal();
System.out.println("*******结束*******");
}
}
其中分别用了Iterator,ListIterator和循环来访问ArrayList和LinkedList,输出结果为
//输出结果
*开始*
ArrayList使用Iterator遍历100000个数据花了9ms
LinkedList使用Iterator遍历100000个数据花了9ms
ArrayList使用ListIterator遍历100000个数据花了8ms
LinkedList使用ListIterator遍历100000个数据花了9ms
ArrayList使用循环遍历100000个数据花了6ms
LinkedList使用循环遍历100000个数据花了8049ms
*结束*
//输出结果
使用迭代器时,ArrayList比LinkedList要快上不少,多次测试大约是三倍的样子,Iterator和ListIterator遍历同样的容器效率差不多。但是通过循环遍历,LinkedList跟ArrayList差了三个数量级,其实用循环遍历,看似是顺序遍历List,但是,通过List.get()的方式过的元素值,其实是通过随机访问来实现的,所以也从这个例子可以证明上面所说LinkedList的随机访问效率是非常差的。
由于数据再增长,最后一项LinkedList循环遍历的测试等待时间非常长,所以把这一项测试去掉,大家知道这个效率非常差就好了。我们把测试数据规模扩大到10billion:
//输出结果
*开始*
ArrayList使用Iterator遍历10000000个数据花了37ms
LinkedList使用Iterator遍历10000000个数据花了121ms
ArrayList使用ListIterator遍历10000000个数据花了38ms
LinkedList使用ListIterator遍历10000000个数据花了117ms
ArrayList使用循环遍历10000000个数据花了30ms
*结束*
//输出结果
扩大数据规模后发现有一点是跟上一次测试结果相同的,就是ArrayList的效率明显比LinkedList要高一些,而且另外发现一个问题,用循环遍历ArrayList比用迭代器也要快一些。这是为什么呢?
1、 为什么ArrayList循环遍历比迭代器遍历快?
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];
}
这是ArrayList中一个继承了Iterator的内部类的next()方法,看了源代码就可以发现其实ArrayList的Iterator就是直接访问ArryList中用于存放数据的一个数组,但是多了一些检查工作。而且,除此之外,遍历时每次iterator.hasNext()也需要额外的时间开销,所以这样遍历自然会比较满一些。
2、 为什么用迭代器遍历ArrayList比LinkedList快一些呢?
这个问题从源码角度还真不好说,搜了一些资料,发现大家对这个效率原因答案都不是特别明确,依稀看到有人的解释我觉得还是蛮有道理的,就是ArrayList的数据结构内存分配是连续的,而LinkedList是不连续的,访问下一个元素的时候需要通过地址去查找,所以有了额外的时间开销。
操作Arrays.asList()
有这么一使用Arrays.asList的例子:
List<Integer>list=Arrays.asList(5,6,8,2,45,1,2,8,5,125,6,8,5);
list.add(5);
通过Arrays.asList方法把一串数字转化成list,然后调用list中的add方法添加元素。但是运行会发现,编译器抛出了一个java.lang.UnsupportedOperationException异常,问题就在add方法,其实除了add方法,remove,retain等对list元素个数会产生变化的方法都是会报这个异常的,究其原因,看一下Arrays.asList的源码
乍一看,发现Arrays.asList返回的就是一个ArrayList对象,但是仔细看发现这个ArrayList并非java.util.ArrayList,而是Arrays的一个内部类,而且没有实现刚才说的add那些方法,而是直接继承了AbstractList,然后AbstractList并没有实现add等方法,只是直接抛出了UnsupportedOperationException异常,所以才会有上述代码发生的那些情况。
正确的使用:
List<Integer> list = new ArrayList<Integer>(Arrays.asList(5, 6, 8, 2,
45, 1, 2, 8, 5, 125, 6, 8, 5));
list.add(5);
总结
对于LinekdList和ArrayList的差异,主要就差别在随机访问与修改,所以建议如下:
1、 如果需要大量删除与添加元素,使用LinkedList
2、 如果需要大量随机访问元素,使用ArrayList
3、 如果需要经常遍历,两者均可,ArrayList效率略高,但是如果选用LinedList,在遍历时尽量使用迭代器,否则相当于随机存取,效率非常低。