List接口的一些列实现中,最常用最重要的就是这三个:ArrayList、Vector、LinkedList。这里我就基于JDK1.7来看一下源码。
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
从这三个类定义就可以看出一些信息:
- ArrayList、Vector继承了AbstractList这个抽象类,LinkedList继承了AbstractSequentialList
这个抽象类,AbstractSequentialList 也是继承AbstractList,只不过它实现了get(int index)、
set(int index, E element)、add(int index, E element) 和 remove(int index)这些骨干性函数,降低了List接口的复杂度; - ArrayList和Vector都实现了RandomAccess接口,而LinkedList没有,这是什么意思呢?在JDK中,RandomAccess接口是一个空接口,
所以它没有实际意义,就是一个标记,标记这个类支持快速随机访问,所以,arrayList和vector是支持随机访问的,但是LinkedList不支持; - serializbale接口表名,他们都支持序列化。
在这三个List实现类里面:
- ArrayList和Vector使用了数组的实现,相当于封装了对数组的操作。这也正是他们能够支持快速随机访问的原因,
在JDK中所有基于数组实现的数据结构都能够支持快速随机访问。ArrayList和Vector的实现上几乎都使用了相同的算法,
他们的主要区别就是ArrayList没有对任何一个方法做同步,所以不是线程安全的;而Vector中大部分方法都做了线程同步,是线程安全的。 -
LinkedList使用的是非循环双向链表的数据结构(这是JDK1.7更新部分,LinkedList在1.7之前都是循环双向链表)。
由于是基于链表的,所以是没法实现随机访问的,只能顺序访问,这也正式它没有实现RandomAccess接口的原因。
ArrayList和Vector方法实现基本一样,所以这里我就拿ArrayList和LinkedList来做对比。
一、add方法
- ArrayList实现add
1 public boolean add(E e) { 2 ensureCapacityInternal(size + 1); // Increments modCount!! 3 elementData[size++] = e; 4 return true; 5 } 6 7 private void ensureCapacityInternal(int minCapacity) { 8 if (elementData == EMPTY_ELEMENTDATA) { 9 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 10 } 11 12 ensureExplicitCapacity(minCapacity); 13 } 14 15 private void ensureExplicitCapacity(int minCapacity) { 16 modCount++; 17 18 // overflow-conscious code 19 if (minCapacity - elementData.length > 0) 20 grow(minCapacity); 21 } 22 23 private void grow(int minCapacity) { 24 // overflow-conscious code 25 int oldCapacity = elementData.length; 26 int newCapacity = oldCapacity + (oldCapacity >> 1); 27 if (newCapacity - minCapacity < 0) 28 newCapacity = minCapacity; 29 if (newCapacity - MAX_ARRAY_SIZE > 0) 30 newCapacity = hugeCapacity(minCapacity); 31 // minCapacity is usually close to size, so this is a win: 32 elementData = Arrays.copyOf(elementData, newCapacity); 33 }
- 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 }
代码中可以看到,LinkedList基于链表的,不需要扩容,直接把元素加到链表最后,把新元素的前节点指向之前的last元素后节点就ok了。
二、get方法
- ArrayList实现get
1 public E get(int index) { 2 rangeCheck(index); 3 4 return elementData(index); 5 } 6 7 E elementData(int index) { 8 return (E) elementData[index]; 9 }
ArrayList的get方法比较方便,通过数组下标能够直接找到数据返回。
- LinkedList实现get
1 public E get(int index) { 2 checkElementIndex(index); 3 return node(index).item; 4 } 5 6 Node<E> node(int index) { 7 // assert isElementIndex(index); 8 9 if (index < (size >> 1)) { 10 Node<E> x = first; 11 for (int i = 0; i < index; i++) 12 x = x.next; 13 return x; 14 } else { 15 Node<E> x = last; 16 for (int i = size - 1; i > index; i--) 17 x = x.prev; 18 return x; 19 } 20 }
ArrayList的get方法需要遍历到具体位置,获得数据返回,这里为了提高效率,需要根据获取的位置判断是从头还是从尾开始遍历,将index与长度size的一半比较,如果index<size/2,就只从位置0往后遍历到位置index处,而如果index>size/2,就只从位置size往前遍历到位置index处即可。
三、remove方法
- ArrayList实现remove
1 public E remove(int index) { 2 rangeCheck(index); 3 4 modCount++; 5 E oldValue = elementData(index); 6 7 int numMoved = size - index - 1; 8 if (numMoved > 0) 9 System.arraycopy(elementData, index+1, elementData, index, 10 numMoved); 11 elementData[--size] = null; // clear to let GC do its work 12 13 return oldValue; 14 } 15 16 public boolean remove(Object o) { 17 if (o == null) { 18 for (int index = 0; index < size; index++) 19 if (elementData[index] == null) { 20 fastRemove(index); 21 return true; 22 } 23 } else { 24 for (int index = 0; index < size; index++) 25 if (o.equals(elementData[index])) { 26 fastRemove(index); 27 return true; 28 } 29 } 30 return false; 31 } 32 33 private void fastRemove(int index) { 34 modCount++; 35 int numMoved = size - index - 1; 36 if (numMoved > 0) 37 System.arraycopy(elementData, index+1, elementData, index, 38 numMoved); 39 elementData[--size] = null; // clear to let GC do its work 40 }
ArrayList实现remove(int)和remove(Object)两种方式,通过System的arrayCopy方法实现元素的移动(本质上是数组的复制),remove(Object)方法实际上是先找到需要删除元素的下标,然后在实现删除功能,实际和remove(int)一样。
- LinkedList实现remove
1 public E remove(int index) { 2 checkElementIndex(index); 3 return unlink(node(index)); 4 } 5 6 public boolean remove(Object o) { 7 if (o == null) { 8 for (Node<E> x = first; x != null; x = x.next) { 9 if (x.item == null) { 10 unlink(x); 11 return true; 12 } 13 } 14 } else { 15 for (Node<E> x = first; x != null; x = x.next) { 16 if (o.equals(x.item)) { 17 unlink(x); 18 return true; 19 } 20 } 21 } 22 return false; 23 } 24 25 E unlink(Node<E> x) { 26 // assert x != null; 27 final E element = x.item; 28 final Node<E> next = x.next; 29 final Node<E> prev = x.prev; 30 31 if (prev == null) { 32 first = next; 33 } else { 34 prev.next = next; 35 x.prev = null; 36 } 37 38 if (next == null) { 39 last = prev; 40 } else { 41 next.prev = prev; 42 x.next = null; 43 } 44 45 x.item = null; 46 size--; 47 modCount++; 48 return element; 49 }
LinkedList实现remove(int)和remove(Object),不管是根据下标删除还是根据Object删除,都是先找到对应的Node,然后在删除,对应的上下数据节点改变。
四、总结
- 对于查找get方法,ArrayList的效率是要比LinkedList高的,原因也是ArrayList是基于数组的,直接通过下标能找到,LinkedList则需要遍历到具体位置。
- 增加add方法和删除remove方法,虽说链表的增加和删除效率比数组高,但是这也不是绝对,看具体情况,有几种极端情况,一种是在LinkedList和ArrayList的首位增加和删除,这种LinkedList效率好一点,如果在中间处增加和删除,这种的话ArrayList效率好一点,所以说增加add方法和删除remove方法说不上哪个效率高,这要看具体情况分析。