世上的事,只要肯用心去學,沒有一件是太晚的。請你必定不要停下來,成爲你想成爲的人。html
在learn from collection framework design中提到,collection framework分爲兩部分,分別爲Collection
和Map
,其中Collection
又分爲三類分別爲List
,Set
和Queue
,本篇文章先來分析ArrayList的實現。java
如上圖所示,它實現了RandomAccess
(可隨機訪問),Cloneable
(可克隆),Serializable
(支持序列化和反序列化)接口以及List
接口,而且它還繼承了List
的抽象模板類AbstractList
。
其中,前三個接口都是marker interface,沒有可讓實現類實現的方法。算法
下面直接來看ArrayList
內部的一些實現機制。數組
其內部維護了一個Object
類型的數組
,即elementData
成員變量,成員變量size
記錄list的大小。。安全
ArrayList的構造方法有以下三種重載,分別是:
第一種方式:根據初始容量初始化ArrayList。數據結構
/** * 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) { // 根據傳入的初始的容量大小初始化List,其內部維護的是 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; // 是一個長度爲0的空數組,即{} } else { // 因數組長度不能小於0,故拋出異常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
第二種:使用默認大小,默認內部數組長度爲0。app
/** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // DEFAULTCAPACITY_EMPTY_ELEMENTDATA默認爲長度爲0的空數組 }
第三種:根據傳入的集合構建ArrayListdom
/** * 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) { // collection包含元素 // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // collection不包含元素,使用內部預約義的長度爲0的數組。 // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
java.util.ArrayList#ensureCapacityInternal
是專門用於擴容的私有方法,具體以下:ide
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
一共有兩個步驟,分別爲計算所需容量以及擴容兩個步。測試
calculateCapacity
源碼以下:
private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); // 若是剛開始是空數組,則第一次擴容,數組長度需擴容到 max(10,須要的最小容量) } return minCapacity; }
private void ensureExplicitCapacity(int minCapacity) { modCount++; // 記錄內部數組擴容次數 // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
這裏爲何要用減法而不直接比較?
由於minCapacity
這個是由原始的大小 + 須要插入的元素的個數獲得的,在加法運算後可能會出現溢出,變爲負數,變爲負數了就不能繼續擴容了。
grow
具體以下:
/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) 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); }
huge
源碼以下:
private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
擴容倍數是1.5,最大數組長度爲 MAX_ARRAY_SIZE,即Integer.MAX_VALUE - 8
,之因此要取這個值是由於,有的JVM在實現數組的時候,剛開始會保留一些header的信息,這些信息會佔8個字節。在擴展數組時,長度一旦超過這個大小,會拋出OutOfMemoryError
異常。
也就是說,若是當前數組不足以容納新的元素,則須要1.5倍擴容,最終容量最大爲Integer.MAX_VALUE - 8
有兩種方式,分別以下:
方式一,默認在結尾插入,以下:
/** * 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; }
方式二,在指定位置插入元素,以下:
/** * Inserts the specified element at the specified position in this * list. Shifts the element currently at that position (if any) and * any subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException {@inheritDoc} */ public void add(int index, E element) { rangeCheckForAdd(index); // 注意,檢查下標的合法性,這個下標是跟ArrayList的長度比較的,不是跟內部數據的capacity比較的! ensureCapacityInternal(size + 1); // Increments modCount!! // 把指定下標後(包括該下標)的數據總體後移一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
也有兩種方式。
方式一,在結尾插入,以下:
/** * Appends all of the elements in the specified collection to the end of * this list, in the order that they are returned by the * specified collection's Iterator. The behavior of this operation is * undefined if the specified collection is modified while the operation * is in progress. (This implies that the behavior of this call is * undefined if the specified collection is this list, and this * list is nonempty.) * * @param c collection containing elements to be added to this list * @return <tt>true</tt> if this list changed as a result of the call * @throws NullPointerException if the specified collection is null */ public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }
方式二,在指定位置插入,以下:
/** * Inserts all of the elements in the specified collection into this * list, starting at the specified position. Shifts the element * currently at that position (if any) and any subsequent elements to * the right (increases their indices). The new elements will appear * in the list in the order that they are returned by the * specified collection's iterator. * * @param index index at which to insert the first element from the * specified collection * @param c collection containing elements to be added to this list * @return <tt>true</tt> if this list changed as a result of the call * @throws IndexOutOfBoundsException {@inheritDoc} * @throws NullPointerException if the specified collection is null */ public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; // 計算須要index後(包括index)空出的元素的個數 if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }
主要有兩種方式,分別爲:
方式一,移出指定下標對應位置的元素,以下:
/** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). * * @param index the index of the element to be removed * @return the element that was removed from the list * @throws IndexOutOfBoundsException {@inheritDoc} */ public E remove(int index) { rangeCheck(index); // index 有效性校驗,跟 內部元素個數 size 比較 modCount++; E oldValue = elementData(index); // 獲取指定下標下的元素 int numMoved = size - index - 1; // 計算須要移動的元素的個數 if (numMoved > 0) // 指定index後的全部元素統一貫前一個索引距離 System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work 設置爲null,容許gc回收不用的對象,並更新list的大小 return oldValue; }
方式二,移出左邊第一個出現的指定元素
/** * Removes the first occurrence of the specified element from this list, * if it is present. If the list does not contain the element, it is * unchanged. More formally, removes the element with the lowest index * <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt> * (if such an element exists). Returns <tt>true</tt> if this list * contained the specified element (or equivalently, if this list * changed as a result of the call). * * @param o element to be removed from this list, if present * @return <tt>true</tt> if this list contained the specified element */ public boolean remove(Object o) { 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; }
注意,其一,判斷相等使用的是equals方法,自定義的對象,須要根據本身的需求從新實現其equals方法;其二,從左向右遍歷,只移出第一個跟指定對象相等(equals)的對象。
其中,fastRemove
方法以下:
/* * Private remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove(int index) { modCount++; // 修改次數+1 int numMoved = size - index - 1; // 計算須要向前移動的元素的個數 if (numMoved > 0) // 若是須要移動,則將index後的元素統一貫前移動一個元素大小位置,並把最後的元素的引用設爲null,便於gc回收再也不使用的對象,並更新list的大小。 System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }
方式一,移除全部元素
/** * Removes all of the elements from this list. The list will * be empty after this call returns. */ public void clear() { modCount++; // 修改次數 + 1 // clear to let GC do its work for (int i = 0; i < size; i++) // 全部索引下標下的元素引用設置爲null elementData[i] = null; size = 0; // 重置list的大小爲0 }
方式二,移出指定範圍內的元素,包括開始索引不包括結束索引
/** * Removes from this list all of the elements whose index is between * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. * Shifts any succeeding elements to the left (reduces their index). * This call shortens the list by {@code (toIndex - fromIndex)} elements. * (If {@code toIndex==fromIndex}, this operation has no effect.) * * @throws IndexOutOfBoundsException if {@code fromIndex} or * {@code toIndex} is out of range * ({@code fromIndex < 0 || * fromIndex >= size() || * toIndex > size() || * toIndex < fromIndex}) */ protected void removeRange(int fromIndex, int toIndex) { modCount++; // 修改次數 + 1 int numMoved = size - toIndex; // 計算須要移動的元素的個數 System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // clear to let GC do its work int newSize = size - (toIndex-fromIndex); // 計算list新的大小 for (int i = newSize; i < size; i++) { // 從後往前依次清除指定位置上的元素 elementData[i] = null; } size = newSize; // 更新list的大小 }
注意,這種方式是一個
protected
類型的,即只容許ArrayList子類或其自己調用的方法。
方式三,批量移出給定集合內的元素或不在給定集合內的元素
private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; int r = 0, w = 0; boolean modified = false; try { for (; r < size; r++) // 從前向後遍歷 if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. if (r != size) { // 剩餘的總體前移 System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // 有元素被移除 // clear to let GC do its work for (int i = w; i < size; i++) // 移除以後的設置爲null elementData[i] = null; modCount += size - w; // 修改次數 + 移除的元素的個數 size = w; // 修改list的大小 modified = true; // 設置修改標誌位爲true } } return modified; }
數據移除採用的是雙指針,指針
w
維護的是新的list,指針r
用於遍歷舊的list,一次外層循環遍歷便可獲得新的list,其中w
是新的list的大小,算法複雜度是O(n)
方式四,移除指定集合內的全部元素
public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, false); }
其內部調用的是方式三的方法,不作過多說明。
方式五,移除指定集合外的全部元素
public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true); }
方式六,移除符合條件的全部數據
@Override public boolean removeIf(Predicate<? super E> filter) { Objects.requireNonNull(filter); // figure out which elements are to be removed // any exception thrown from the filter predicate at this stage // will leave the collection unmodified int removeCount = 0; final BitSet removeSet = new BitSet(size); final int expectedModCount = modCount; final int size = this.size; for (int i=0; modCount == expectedModCount && i < size; i++) { @SuppressWarnings("unchecked") final E element = (E) elementData[i]; if (filter.test(element)) { removeSet.set(i); removeCount++; } } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } // shift surviving elements left over the spaces left by removed elements final boolean anyToRemove = removeCount > 0; if (anyToRemove) { final int newSize = size - removeCount; for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) { i = removeSet.nextClearBit(i); elementData[j] = elementData[i]; } for (int k=newSize; k < size; k++) { elementData[k] = null; // Let gc do its work } this.size = newSize; if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; } return anyToRemove; }
/** * Save the state of the <tt>ArrayList</tt> instance to a stream (that * is, serialize it). * * @serialData The length of the array backing the <tt>ArrayList</tt> * instance is emitted (int), followed by all of its elements * (each an <tt>Object</tt>) in the proper order. */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
注意,在序列化的時候,list大小不能修改,序列化的時候把list的大小size也保存下來了。
/** * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, * deserialize it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff s.defaultReadObject(); // Read in capacity s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity int capacity = calculateCapacity(elementData, size); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity); ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }
反序列化後,list的capacity和size是同樣的。
測試代碼以下:
package com.company; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) throws Exception { // write your code here List<Integer> list = new ArrayList<>(); for (int i = 0; i < 12; i++) { list.add(i); } System.out.println(list.size()); System.out.println(list); ByteArrayOutputStream os = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(list); oos.flush(); byte[] bytes = os.toByteArray(); ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(bytes)); List<Integer> o = (List<Integer>)inputStream.readObject(); System.out.println(o.size()); System.out.println(o); Field elementData1 = o.getClass().getDeclaredField("elementData"); elementData1.setAccessible(true); Object[] elementData = (Object[]) elementData1.get(list); System.out.println(elementData.length); elementData = (Object[]) elementData1.get(o); System.out.println(elementData.length); } }
替換,本質上就是一個變換,只不過這個是在原數據上修改。
@Override @SuppressWarnings("unchecked") public void replaceAll(UnaryOperator<E> operator) { Objects.requireNonNull(operator); final int expectedModCount = modCount; final int size = this.size; for (int i=0; modCount == expectedModCount && i < size; i++) { elementData[i] = operator.apply((E) elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; }
排序,其實現了通用的排序算法(調用Array.sort
方法),排序比較規則交給用戶來指定。
@Override @SuppressWarnings("unchecked") public void sort(Comparator<? super E> c) { final int expectedModCount = modCount; Arrays.sort((E[]) elementData, 0, size, c); if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; }
Itr
實現了能夠向後遍歷
和remove
操做的迭代器,由iterator
方法返回。ListItr
實現了能夠向前遍歷
和向後遍歷
、元素的添加刪除修改
的迭代器,由listIterator
方法返回。關於遍歷,不得不說一個很是有名的異常 -
ConcurrentModificationException
, 多數狀況下是因爲list內部數組長度發生變化致使,modCount != expectedModCount
或者是IndexOutOfBoundsException等等緣由拋出的這個異常,遵循一個原則,在使用迭代器的時候,不能直接調用list的方法來修改list而要經過迭代器提供的響應方法來修改list。
本篇文章,相對來講比較簡單,歸根結底,對ArrayList的各類操做都是對底層數組的操做,深入理解數組這種很是簡單的數據結構對理解ArrayList的各個操做有很大幫助。