ArrayList 是實現 List 接口的動態數組,所謂動態就是它的大小是可變的。實現了全部可選列表操做,並容許包括 null 在內的全部元素。除了實現 List 接口外,此類還提供一些方法來操做內部用來存儲列表的數組的大小。數組
每一個 ArrayList 實例都有一個容量,該容量是指用來存儲列表元素的數組的大小。默認初始容量爲 10。隨着 ArrayList 中元素的增長,它的容量也會不斷的自動增加。在每次添加新的元素時,ArrayList 都會檢查是否須要進行擴容操做,擴容操做帶來數據向新數組的從新拷貝,因此若是咱們知道具體業務數據量,在構造 ArrayList 時能夠給 ArrayList 指定一個初始容量,這樣就會減小擴容時數據的拷貝問題。固然在添加大量元素前,應用程序也可使用 ensureCapacity 操做來增長 ArrayList 實例的容量,這能夠減小遞增式再分配的數量。app
注意,ArrayList 實現不是同步的。若是多個線程同時訪問一個 ArrayList 實例,而其中至少一個線程從結構上修改了列表,那麼它必須保持外部同步。因此爲了保證同步,最好的辦法是在建立時完成,以防止意外對列表進行不一樣步的訪問:函數
List list = Collections.synchronizedList(new ArrayList<>());
ArrayList 咱們使用的實在是太多了,很是熟悉,因此在這裏將不介紹它的使用方法。ArrayList 是實現 List 接口的,底層採用數組實現,因此它的操做基本上都是基於對數組的操做。源碼分析
/** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size;
transient??爲 Java 關鍵字,爲變量修飾符,若是用 transient 聲明一個實例變量,當對象存儲時,它的值不須要維持。Java 的 serialization 提供了一種持久化對象實例的機制。當持久化對象時,可能有一個特殊的對象數據成員,咱們不想用 serialization 機制來保存它。爲了在一個特定對象的一個域上關閉 serialization,能夠在這個域前加上關鍵字 transient。當一個對象被序列化的時候,transient 型變量的值不包括在序列化的表示中,然而非 transient 型的變量是被包括進去的。性能
這裏 Object[] elementData,就是咱們的 ArrayList 容器,下面介紹的基本操做都是基於該 elementData 變量來進行操做的。ui
ArrayList 提供了三個構造函數:this
ArrayList():默認構造函數,提供初始容量爲 10 的空列表。google
ArrayList(int initialCapacity):構造一個具備指定初始容量的空列表。spa
ArrayList(Collection<? extends E> c):構造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。線程
/** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
ArrayList 提供了 add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)、set(int index, E element) 這個五個方法來實現 ArrayList 增長。
add(E e):將指定的元素添加到此列表的尾部。
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
這裏 ensureCapacity() 方法是對 ArrayList 集合進行擴容操做,elementData(size++) = e,將列表末尾元素指向e。
add(int index, E element):將指定的元素插入此列表中的指定位置。
public void add(int index, E element) { //判斷索引位置是否正確 if (index > size || index < 0) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); //擴容檢測 ensureCapacity(size+1); /* * 對源數組進行復制處理(位移),從index + 1到size-index。 * 主要目的就是空出index位置供數據插入, * 即向右移動當前位於該位置的元素以及全部後續元素。 */ System.arraycopy(elementData, index, elementData, index + 1, size - index); //在指定位置賦值 elementData[index] = element; size++; }
在這個方法中最根本的方法就是 System.arraycopy() 方法,該方法的根本目的就是將 index 位置空出來以供新數據插入,這裏須要進行數組數據的右移,這是很是麻煩和耗時的,因此若是指定的數據集合須要進行大量插入(中間插入)操做,推薦使用 LinkedList。
addAll(Collection<? extends E> c):按照指定 collection 的迭代器所返回的元素順序,將該 collection 中的全部元素添加到此列表的尾部。
public boolean addAll(Collection<? extends E> c) { // 將集合C轉換成數組 Object[] a = c.toArray(); int numNew = a.length; // 擴容處理,大小爲size + numNew ensureCapacity(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }
這個方法無非就是使用 System.arraycopy() 方法將 C 集合(先準換爲數組)裏面的數據複製到 elementData 數組中。這裏就稍微介紹下 System.arraycopy(),由於下面還將大量用到該方法。該方法的原型爲:public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)。它的根本目的就是進行數組元素的複製。即從指定源數組中複製一個數組,複製從指定的位置開始,到目標數組的指定位置結束。將源數組 src從srcPos 位置開始複製到 dest 數組中,複製長度爲 length,數據從 dest 的 destPos 位置開始粘貼。
addAll(int index, Collection<? extends E> c):從指定的位置開始,將指定 collection 中的全部元素插入到此列表中。
public boolean addAll(int index, Collection<? extends E> c) { //判斷位置是否正確 if (index > size || index < 0) throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); //轉換成數組 Object[] a = c.toArray(); int numNew = a.length; //ArrayList容器擴容處理 ensureCapacity(size + numNew); // Increments modCount //ArrayList容器數組向右移動的位置 int numMoved = size - index; //若是移動位置大於0,則將ArrayList容器的數據向右移動numMoved個位置,確保增長的數據可以增長 if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); //添加數組 System.arraycopy(a, 0, elementData, index, numNew); //容器容量變大 size += numNew; return numNew != 0; }
set(int index, E element):用指定的元素替代此列表中指定位置上的元素。
public E set(int index, E element) { //檢測插入的位置是否越界 RangeCheck(index); E oldValue = (E) elementData[index]; //替代 elementData[index] = element; return oldValue; }
ArrayList 提供了 remove(int index)、remove(Object o)、removeRange(int fromIndex, int toIndex)、removeAll() 四個方法進行元素的刪除。
remove(int index):移除此列表中指定位置上的元素。
public E remove(int index) { //位置驗證 RangeCheck(index); modCount++; //須要刪除的元素 E oldValue = (E) elementData[index]; //向左移的位數 int numMoved = size - index - 1; //若須要移動,則想左移動numMoved位 if (numMoved > 0) System.arraycopy(elementData, index + 1, elementData, index, numMoved); //置空最後一個元素 elementData[--size] = null; // Let gc do its work return oldValue; }
remove(Object o):移除此列表中首次出現的指定元素(若是存在)。
public boolean remove(Object o) { //由於ArrayList中容許存在null,因此須要進行null判斷 if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { //移除這個位置的元素 fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
其中 fastRemove() 方法用於移除指定位置的元素。以下
private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work }
removeRange(int fromIndex, int toIndex):移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之間的全部元素。
protected void removeRange(int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; System .arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // Let gc do its work int newSize = size - (toIndex - fromIndex); while (size != newSize) elementData[--size] = null; }
removeAll():是繼承自 AbstractCollection 的方法,ArrayList 自己並無提供實現。
public boolean removeAll(Collection<?> c) { boolean modified = false; Iterator<?> e = iterator(); while (e.hasNext()) { if (c.contains(e.next())) { e.remove(); modified = true; } } return modified; }
ArrayList 提供了 get(int index) 用讀取 ArrayList 中的元素。因爲 ArrayList 是動態數組,因此咱們徹底能夠根據下標來獲取 ArrayList 中的元素,並且速度還比較快,故 ArrayList 長於隨機訪問。
public E get(int index) { RangeCheck(index); return (E) elementData[index]; }
在上面的新增方法的源碼中咱們發現每一個方法中都存在這個方法: ensureCapacity(),該方法就是 ArrayList 的擴容方法。在前面就提過 ArrayList 每次新增元素時都會須要進行容量檢測判斷,若新增元素後元素的個數會超過 ArrayList 的容量,就會進行擴容操做來知足新增元素的需求。因此當咱們清楚知道業務數據量或者須要插入大量元素前,我可使用 ensureCapacity 來手動增長 ArrayList 實例的容量,以減小遞增式再分配的數量。
public void ensureCapacity(int minCapacity) { //修改計時器 modCount++; //ArrayList容量大小 int oldCapacity = elementData.length; /* * 若當前須要的長度大於當前數組的長度時,進行擴容操 做 */ if (minCapacity > oldCapacity) { Object oldData[] = elementData; //計算新的容量大小,爲當前容量的1.5倍 int newCapacity = (oldCapacity * 3) / 2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; //數組拷貝,生成新的數組 elementData = Arrays.copyOf(elementData, newCapacity); } }
在這裏有一個疑問,爲何每次擴容處理會是 1.5 倍,而不是 2.五、三、4 倍呢?經過 google 查找,發現 1.5 倍的擴容是最好的倍數。由於一次性擴容太大(例如 2.5 倍)可能會浪費更多的內存(1.5 倍最多浪費 33%,而 2.5 被最多會浪費 60%,3.5 倍則會浪費 71%……)。可是一次性擴容過小,須要屢次對數組從新分配內存,對性能消耗比較嚴重。因此 1.5 倍剛恰好,既能知足性能需求,也不會形成很大的內存消耗。
處理這個 ensureCapacity() 這個擴容數組外,ArrayList 還給咱們提供了將底層數組的容量調整爲當前列表保存的實際元素的大小的功能。它能夠經過 trimToSize() 方法來實現。該方法能夠最小化 ArrayList 實例的存儲量。
public void trimToSize() { modCount++; int oldCapacity = elementData.length; if (size < oldCapacity) { elementData = Arrays.copyOf(elementData, size); } }