List接口是Java中經常用到的接口,如果對具體的List實現類的特性不了解的話,可能會導致程序性能的下降,下面從原理上簡單的介紹List的具體實現:
可以看到,List繼承了Collection接口,而Collection接口繼承了Iterable接口。其中還有AbstractCollection和AbstractList的實現,用於List對象的公共部分代碼的復用:
public interface List<E> extends Collection<E> { // Query Operations
public interface Collection<E> extends Iterable<E> { // Query Operations
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { /** * Sole constructor. (For invocation by subclass constructors, typically * implicit.) */ protected AbstractList() { }
public abstract class AbstractCollection<E> implements Collection<E> { /** * Sole constructor. (For invocation by subclass constructors, typically * implicit.) */ protected AbstractCollection() { }
具體這三個接口定義的方法如下:
現在來看看List接口的具體特性:
1、Vector
Vector類是通過數組實現的,支持線程的同步,即某一時刻只有一個線程能夠寫Vector,避免多線程同時寫而引起的不一致性,但實現同步需要很高的花費,因此,訪問它的效率不是很高。
現在來看一下Vector的成員變量和其中部分的構造方法:
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { /** * The array buffer into which the components of the vector are * stored. The capacity of the vector is the length of this array buffer, * and is at least large enough to contain all the vector's elements. * * <p>Any array elements following the last element in the Vector are null. * * @serial */ protected Object[] elementData; /** * The number of valid components in this {@code Vector} object. * Components {@code elementData[0]} through * {@code elementData[elementCount-1]} are the actual items. * * @serial */ protected int elementCount; /** * The amount by which the capacity of the vector is automatically * incremented when its size becomes greater than its capacity. If * the capacity increment is less than or equal to zero, the capacity * of the vector is doubled each time it needs to grow. * * @serial */ protected int capacityIncrement; /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -2767605614048989439L; /** * Constructs an empty vector with the specified initial capacity and * capacity increment. * * @param initialCapacity the initial capacity of the vector * @param capacityIncrement the amount by which the capacity is * increased when the vector overflows * @throws IllegalArgumentException if the specified initial capacity * is negative */ public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; }
對數據結構有一定了解的人都知道,使用數組作為存儲底層,當存儲空間不夠了的時候,是會重新分配一塊更大的內存空間,然后把原空間的數據全部都copy過去,最后釋放原空間,這樣會導致程序性能的下降,或者是GC的消耗(Vector是默認擴展1倍)。所以我們對處理的數據量要有一定的預估,初始化Vector的時候指定容量大小。由於對Vector所有動作都添加synchronized關鍵字,同樣會導致執行的性能下降。
下面是對Vector添加元素的時候的代碼:
/** * Appends the specified element to the end of this Vector. * * @param e element to be appended to this Vector * @return {@code true} (as specified by {@link Collection#add}) * @since 1.2 */ public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
/** * This implements the unsynchronized semantics of ensureCapacity. * Synchronized methods in this class can internally call this * method for ensuring capacity without incurring the cost of an * extra synchronization. * * @see #ensureCapacity(int) */ private void ensureCapacityHelper(int minCapacity) { int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object[] oldData = elementData; int newCapacity = (capacityIncrement > 0) ? (oldCapacity + capacityIncrement) : (oldCapacity * 2); if (newCapacity < minCapacity) { newCapacity = minCapacity; } elementData = Arrays.copyOf(elementData, newCapacity); } }
2、Stack
Stack 類表示后進先出(LIFO)的對象堆棧。它通過五個操作對類 Vector 進行了擴展,同樣是線程安全的 ,允許將向量視為堆棧。它提供了通常的 push 和 pop 操作,以及取堆棧頂點的 peek 方法、測試堆棧是否為空的 empty 方法、在堆棧中查找項並確定到堆棧頂距離的 search 方法。當操作的集合不存在元素的時候,拋出EmptyStackException。
public class Stack<E> extends Vector<E> { /** * Creates an empty Stack. */ public Stack() { }
3、ArrayList
同樣,ArrayList內部是通過數組實現的,它允許對元素進行快速隨機訪問。數組的缺點是每個元素之間不能有間隔,當數組大小不滿足時需要增加存儲能力,就要講已經有數組的數據復制到新的存儲空間中。當從ArrayList的中間位置插入或者刪除元素時,需要對數組進行復制、移動、代價比較高(ArrayList在內存不夠時默認是擴展50% + 1個)。因此,它適合隨機查找和遍歷,不適合插入和刪除。由於沒有添加同步,所以是非線程安全的,執行效率也要比Vector高很多
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. */ private transient Object[] elementData; /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size; /** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @exception IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; }
public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } }
4、LinkedList
LinkedList是用雙向鏈表結構存儲數據的,很適合數據的動態插入和刪除,隨機訪問和遍歷速度比較慢。另外,他還提供了List接口中沒有定義的方法,專門用於操作表頭和表尾元素,可以當作堆棧、隊列和雙向隊列使用。
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable { private transient Entry<E> header = new Entry<E>(null, null, null); private transient int size = 0; /** * Constructs an empty list. */ public LinkedList() { header.next = header.previous = header; }
Entry(E element, Entry<E> next, Entry<E> previous) { this.element = element; this.next = next; this.previous = previous; } }
總結:根據不同實現類的特性,選擇對應的數據結構,能提高程序的運行效率 。需要注意的是,一條線程在進行list列表元素迭代的時候,另外一條線程如果想要在迭代過程中,想要對元素進行操作的時候,比如滿足條件添加新元素,會發生ConcurrentModificationException並發修改異常。當然后來版本的CopyOnWrite容器解決了該問題,后續會介紹。