为方便开发人员,JDK提供了一套主要数据结构的实现,好比List、Map等。今儿说说List接口。前端
List接口的一些列实现中,最经常使用最重要的就是这三个:ArrayList、Vector、LinkedList。java
JDK中这三个类的定义:node
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
从这三个类定义就能够看出一些信息:算法
下面详细说说这三个List实现。数组
这三个里面,ArrayList和Vector使用了数组的实现,至关于封装了对数组的操做。这也正是他们可以支持快速随机访问的缘由,多说一句,JDK中全部基于数组实现的数据结构都可以支持快速随机访问。安全
ArrayList和Vector的实现上几乎都使用了相同的算法,他们的主要区别就是ArrayList没有对任何一个方法作同步,因此不是线程安全的;而Vector中大部分方法都作了线程同步,是线程安全的。数据结构
LinkedList使用的是双向循环链表的数据结构。因为是基于链表的,因此是无法实现随机访问的,只能顺序访问,这也正式它没有实现RandomAccess接口的缘由。dom
正式因为ArrayList、Vector和LinkedList所采用的数据结构不一样,注定他们适用的是彻底不一样的场景。性能
经过阅读这几个类的源码,咱们能够看到他们实现的不一样。ArrayList和Vector基本同样,咱们就拿ArrayList和LinkedList作对比。测试
在末尾增长一个元素
ArrayList中的add方法实现以下:
1 public boolean add(E e) { 2 ensureCapacityInternal(size + 1); // Increments modCount!! 3 elementData[size++] = e; 4 return true; 5 }
这个方法作两件事情,首先确保数组空间足够大,而后在数组末尾增长元素而且经过后++使得完成size+1。
从这个代码能够看出,若是数组空间足够大,那么只是数组的add操做就是O(1)的性能,很是高效。
在看看ensureCapacityInternal这个方法的实现:
1 private void ensureCapacityInternal(int minCapacity) { 2 modCount++; 3 // overflow-conscious code 4 if (minCapacity - elementData.length > 0) 5 grow(minCapacity); 6 } 7 8 private void grow(int minCapacity) { 9 // overflow-conscious code 10 int oldCapacity = elementData.length; 11 int newCapacity = oldCapacity + (oldCapacity >> 1); 12 if (newCapacity - minCapacity < 0) 13 newCapacity = minCapacity; 14 if (newCapacity - MAX_ARRAY_SIZE > 0) 15 newCapacity = hugeCapacity(minCapacity); 16 // minCapacity is usually close to size, so this is a win: 17 elementData = Arrays.copyOf(elementData, newCapacity); 18 }
能够看出,若是数组空间不够,那么这个方法就会作数组扩容和数组复制操做,看第11行,JDK利用移位运算符进行扩容计算,>>1右移一位表示除2,因此newCapacity就是扩容为原来的1.5倍。
PS:这里的代码都是JDK1.7中的实现,JDK1.7对1.6的不少代码作了优化,好比上面这段扩容代码,在JDK1.6中第11行是直接除2,显然,移位运算要更高效。
在看看LinkedList中的add方法:
1 public boolean add(E e) { 2 linkLast(e); 3 return true; 4 } 5 6 void linkLast(E e) { 7 final Node<E> l = last; 8 final Node<E> newNode = new Node<>(l, e, null); 9 last = newNode; 10 if (l == null) 11 first = newNode; 12 else 13 l.next = newNode; 14 size++; 15 modCount++; 16 }
1 Node(Node<E> prev, E element, Node<E> next) { 2 this.item = element; 3 this.next = next; 4 this.prev = prev; 5 }
从这段add代码能够看出,LinkedList因为使用了链表,因此不须要进行扩容,直接把元素加到链表最后,把新元素的前驱指向以前的last元素,并把last元素指向新元素就ok。这也是一个O(1)的性能。
测试一下:
1 public static void main(String[] args) { 2 // TODO Auto-generated method stub 3 long begin = System.currentTimeMillis(); 4 5 // List<Object> list = new ArrayList<Object>(); 6 List<Object> list = new LinkedList<Object>(); 7 Object obj = new Object(); 8 for(int i=0; i<50000; i++){ 9 list.add(obj); 10 } 11 12 long end = System.currentTimeMillis(); 13 long time = end - begin; 14 System.out.println(time+""); 15 16 }
分别对ArrayList和LinkedList作末尾add操做,循环50000次,ArrayList耗时6ms,而LinkedList耗时8ms,这是因为LinkedList在add时候须要更多的对象建立和赋值操做。
在任意位置插入元素
ArrayList中的实现以下:
1 public void add(int index, E element) { 2 rangeCheckForAdd(index); 3 4 ensureCapacityInternal(size + 1); // Increments modCount!! 5 System.arraycopy(elementData, index, elementData, index + 1, 6 size - index); 7 elementData[index] = element; 8 size++; 9 }
这段代码,首先先检查数组容量,容量不够先扩容,而后把index以后的数组日后挪一个,最后在index位置放上新元素。因为数组是一块连续内存空间,因此在任意位置插入,都会致使这个其后数组后挪一位的状况,须要作一次数组复制操做,很明显,若是有大量的随机插入,那么这个数组复制操做开销会很大,并且插入的越靠前,数组复制开销越大。
LinkedList中的实现:
1 public void add(int index, E element) { 2 checkPositionIndex(index); 3 4 if (index == size) 5 linkLast(element); 6 else 7 linkBefore(element, node(index)); 8 } 9 10 void linkBefore(E e, Node<E> succ) { 11 // assert succ != null; 12 final Node<E> pred = succ.prev; 13 final Node<E> newNode = new Node<>(pred, e, succ); 14 succ.prev = newNode; 15 if (pred == null) 16 first = newNode; 17 else 18 pred.next = newNode; 19 size++; 20 modCount++; 21 }
这段代码,取到原先index处节点的前驱,变成新节点的前驱 ,同时把原先index变成新节点的后驱,这样就完成了新节点的插入。这个就是链表的优点,不存在数据复制操做,性能和在最后插入是同样的。
测试一种极端状况,每次都在最前端插入元素:
1 public static void main(String[] args) { 2 // TODO Auto-generated method stub 3 long begin = System.currentTimeMillis(); 4 5 // List<Object> list = new ArrayList<Object>(); 6 List<Object> list = new LinkedList<Object>(); 7 Object obj = new Object(); 8 for(int i=0; i<50000; i++){ 9 list.add(0,obj); 10 } 11 12 long end = System.currentTimeMillis(); 13 long time = end - begin; 14 System.out.println(time+""); 15 16 }
测试结果是:ArrayList耗时1400ms,而LinkedList只耗时12ms。能够看出,在随机插入的时候,二者的性能差别就很明显了。
小结一下,从上面的源码剖析和测试结果能够看出这三种List实现的一些典型适用场景,若是常常对数组作随机插入操做,特别是插入的比较靠前,那么LinkedList的性能优点就很是明显,而若是都只是末尾插入,则ArrayList更占据优点,若是须要线程安全,则非Vector莫属。