ArrayList提供了三種方式的構造器,能夠構造一個默認初始容量爲10的空列表、構造一個指定初始容量的空列表以及構造一個包含指定collection的元素的列表,這些元素按照該collection的迭代器返回它們的順序排列的。java
ArrayList底層是使用一個Object類型的數組來存放數據的。數組
transient Object[] elementData; // non-private to simplify nested class access
size變量表明List實際存放元素的數量安全
private int size;
不指定ArrayList大小時,默認數組大小爲10數據結構
private static final int DEFAULT_CAPACITY = 10;
接下來看幾個經常使用的方法:jvm
1.函數
public boolean add(E e) { ensureCapacityInternal(size + 1); // 擴容檢測 elementData[size++] = e; //新增元素加到末尾 return true; }
2.源碼分析
public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); //擴容檢測 System.arraycopy(elementData, index, elementData, index + 1, size - index); //使用System.arraycopy的方法,將index後面元素日後移動1位 elementData[index] = element; // 存放元素到index位置 size++; }
get和set方法,都是經過數組下標,直接操做數據的,時間複雜度爲O(1)性能
public E get(int index) { rangeCheck(index); return elementData(index); }
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
咱們重點關注ArrayList的擴容策略,這也是咱們在實際工做中決定是否選擇ArrayList須要考慮的this
像以前add方法裏,每次增長元素時都會進行擴容檢測,若是數組大小不足,則會自動擴容;若是擴容後的大小超出數組最大的大小,則會拋出異常。spa
ensureCapacityInternal(size + 1); // 擴容檢測
那麼ArrayList最大長度能夠看一下定義,是Integer的最大長度-8,Integer是4個字節,那麼ArrayList的最大長度就是2^32-8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
接下來看一下具體的擴容過程:
ArrayList擴容方案,主要有兩個步驟:1.大小檢測,2.擴容
大小檢測:
檢測數組大小是否爲0,若是是,則使用默認的擴容大小10
檢測是否須要擴容,只有當數組最小須要容量大小大於當前數組大小時,纔會進行擴容
擴容:grow和hugeCapacity
進行數組越界判斷
拷貝原始數據到新的數組中
private void ensureCapacityInternal(int minCapacity) { // 經過ArrayList<Integer> a = new ArrayList<Integer>()或者經過序列化讀取,元素大小爲0時,底層數組纔會爲null數組 // 若是底層數組大小爲0,則使用默認的容量大小10 if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { // 數據結構發生改變,和fail-fast機制有關,在使用迭代器過程當中,只能經過迭代器的方法(好比迭代器中add,remove等),修改List的數據結構, // 若是使用List的方法(好比List中的add,remove等),修改List的數據結構,會拋出ConcurrentModificationException modCount++; // 當前數組容量大小不足時,纔會調用grow方法,自動擴容 if (minCapacity - elementData.length > 0) grow(minCapacity); }
能夠看到擴容長度是經過位運算完成的
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; // 新的容量大小 = 原容量大小的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) //溢出判斷,好比minCapacity = Integer.MAX_VALUE / 2, oldCapacity = minCapacity - 1 newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
能夠看到在ArrayList中,有個modCount的變量,每次進行add,set,remove等操做,都會執行modCount++。
在獲取ArrayList的迭代器時,會將ArrayList中的modCount保存在迭代中,
每次執行add,set,remove等操做,都會執行一次檢查,調用checkForComodification方法,對modCount進行比較。若是迭代器中的modCount和List中的modCount不一樣,則拋出ConcurrentModificationException
final void checkForComodification() { if (expectedModCount != ArrayList.this.modCount) throw new ConcurrentModificationException(); }
在對集合進行迭代過程當中,除了迭代器能夠對集合進行數據結構上進行修改,其餘的對集合的數據結構進行修改,都會拋出ConcurrentModificationException錯誤。
transient Object[] elementData;
transient修飾符讓elementData沒法自動序列化,這樣的緣由是,數組內存儲的的元素其實只是一個引用,單單序列化一個引用沒有任何意義,反序列化後這些引用都沒法在指向原來的對象。ArrayList使用writeObject()實現手工序列化數組內的元素。
經過以上源碼分析,其實就能夠總結出ArrayList的優缺點以及使用場景
1.get,set,時間複雜度爲O(1),查找元素快速
2.數據是順序存儲的
1.add(通常都是在末尾插入),時間複雜度爲O(1),最差狀況下(往頭部插入數據),時間複雜度O(n)
2.remove,時間複雜度爲O(n),最優狀況下(移除末尾元素),時間複雜度爲O(1)
3.ArrayList底層使用數組存儲數據,數組是不能自動擴容的,所以在發生擴容的狀況下,須要移動大量的元素。
4.數組大小是由限制的,受jvm和機器的影響,當擴容超出上限時,ArrayList會拋出異常
5.ArrayList全部的操做,都不是同步的,所以ArrayList不是線程安全的。
插入操做多,數據量不大,順序存儲時,能夠考慮使用ArrayList
LinkedList有兩個構造參數,一個爲無參構造,只是新建一個空對象,第二個爲有參構造,新建一個空對象,而後把全部元素添加進去。
public LinkedList() { } public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
LinkedList的存儲單元爲一個名爲Node的內部類,包含pre指針,next指針,和item元素,實現爲雙向鏈表
//鏈表的節點個數 transient int size = 0; //指向頭節點的指針 transient Node<E> first; //指向尾節點的指針 transient Node<E> last;
Node 是在 LinkedList 裏定義的一個靜態內部類,它表示鏈表每一個節點的結構,包括一個數據域item,一個後置指針next,一個前置指針prev。
private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
ArrayList和LinkedList在性能上各有優缺點,都有各自所適用的地方:
1.ArrayList的空間浪費主要體如今在list列表的結尾預留必定的容量空間,而LinkedList的空間花費則體如今它的每個元素都須要消耗至關的空間。
2.對ArrayList和LinkedList而言,在列表末尾增長一個元素所花的開銷都是固定的。對ArrayList而言,主要是在內部數組中增長一項,指向所添加的元素,偶爾可能會致使對數組從新進行分配;而對LinkedList而言,這個開銷是統一的,分配一個內部Entry對象。
當操做是在一列數據的後面添加數據而不是在前面或中間,而且須要隨機地訪問其中的元素時,使用ArrayList會提供比較好的性能;當你的操做是在一列數據的前面或中間添加或刪除數據,而且按照順序訪問其中的元素時,就應該使用LinkedList了。