同步性
ArrayList,LinkedList是不同步的,而Vector是同步的。所以如果不要求线程安全的话,可以使用ArrayList或LinkedList,可以节省为同步而耗费的开销。但在多线程的情况下,有时候就不得不使用Vector了。当然,也可以通过一些办法包装ArrayList,LinkedList,使他们也达到同步,但效率可能会有所降低。
Q1.同步性是什么?为什么会对线程安全有影响?
同步性是指多个线程访问同一个对象。比如一个线程在遍历时,另一个线程同时对其进行操作,可能因为多个线程同时操作而产生问题。
Q2.线程安全是什么?
线程安全是指多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
数据增长
从内部实现机制来讲ArrayList和Vector都是使用Object的数组形式来存储的。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
Q1.ArrayList难道不能设置集合的初始大小来避免不必要的资源开销吗?
可以。如果我们知道一个ArrayList将会有多少个元素,我们可以通过构造方法来指定容量。我们还可以通过trimToSize方法在ArrayList分配完毕之后去掉浪费掉的空间。
Q2.LinkedList是以什么形式来存储的?它的扩展方式有什么不同?
LinkedList定义了一个双向链表。当我们将一个元素加到LinkedList,只是简单的为这个元素分配一个记录,然后调整两个连接。
效率
一般大家都知道ArrayList和LinkedList的大致区别:
- ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
- 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
- 对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。
ArrayList和Vector中,从指定的位置(用index)检索一个对象,或在集合的末尾插入、删除一个对象的时间是一样的,可表示为O(1)。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。为什么会这样呢?因为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行(n-i)个对象的位移操作。 这就是上面所说的移动数据。
而在LinkedList中,因为采用了链表的形式,插入、删除集合中任何位置的元素所花费的时间都是一样的——O(1);但它在索引一个元素的时候比较慢,为O(i),其中i是索引的位置。
下面比较ArrayList和LinkedList在性能上的差异。
时间复杂度
首先一点关键的是,ArrayList的内部实现是基于基础的对象数组的,因此,它使用get方法访问列表中的任意一个元素时 (random access),它的速度要比LinkedList快。LinkedList中的get方法是按照顺序从列表的一端开始检查,直到另外一端。对 LinkedList而言,访问列表中的某个指定元素没有更快的方法了。
假设我们有一个很大的列表,它里面的元素已经排好序了,这个列表可能是ArrayList类型的也可能是LinkedList类型的。现在我们对这个列表来进行二分查找(binary search),比较列表是ArrayList和LinkedList的查询速度;对其进行大量的插入和删除操作,我们重复的在一个列表的开端插入一个元素(这是极端的例子),比较列表是ArrayList和LinkedList的添加速度。看下面的程序:
1 | import java.util.LinkedList; |
当我们以500000条数据为例时,测试运行结果为:
ArrayList和LinkedList查询和插入的结果
这个结果不是固定的,但是基本上ArrayList的查找时间要明显小于LinkedList的时间。因此在这种情况下不宜用LinkedList。二分查找法使用的随机访问(random access)策略,而LinkedList是不支持快速的随机访问的。对一个LinkedList做随机访问所消耗的时间与这个list的大小是成比例 的。而相应的,在ArrayList中进行随机访问所消耗的时间是固定的。
这是否表明ArrayList总是比LinkedList性能要好呢?这并不一定,在不断往列表开头添加元素的情况下,LinkedList的表现要优于ArrayList。有些算法在LinkedList中实现时效率更高,比方说,利用 Collections.reverse方法对列表进行反转时,其性能就要好些。
当一个元素被加到ArrayList的最开端时,所有已经存在的元素都会后移,这就意味着数据移动和复制上的开销。相反的,将一个元素加到LinkedList的最开端只是简单的为这个元素分配一个记录,然后调整两个连接。在 LinkedList的开端增加一个元素的开销是固定的,而在ArrayList的开端增加一个元素的开销是与ArrayList的大小成比例的。
空间复杂度
在LinkedList中有一个私有类,定义如下:
1 | private static class Node<E> { |
每个Node对象 reference列表中的一个元素,同时还有在LinkedList中它的上一个元素和下一个元素。一个有1000个元素的LinkedList对象将有1000个链接在一起的Node对象,每个对象都对应于列表中的一个元素。这样的话,在一个LinkedList结构中将有一个很大的空间开销,因为它要存储这1000个Node对象的相关信息。
ArrayList使用一个内置的数组来存储元素,这个数组的起始容量是10.当数组需要增长时,新的容量按如下公式获得:新容量=(旧容量*3)/2+1
。也就是如前面所说,每一次容量大概会增长50%。这就意味着,如果你有一个包含大量元素的ArrayList对象, 那么最终将有很大的空间会被浪费掉,这个浪费是由ArrayList的工作方式本身造成的。如果没有足够的空间来存放新的元素,数组将不得不被重新进行分配以便能够增加新的元素。对数组进行重新分配,将会导致性能急剧下降。
参考文献
Alan·Jones:描述一下ArrayList和LinkedList各自实现和区别
全力以赴001:Java中ArrayList和LinkedList区别