注:博主java集合框架源碼剖析系列的源碼全部基於JDK1.8.0版本。
本博客將從源碼角度帶領大家學習關於ArrayList的知識。
一ArrayList類的定義:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
從上述代碼可以看到:ArrayList繼承自AbstractList同時實現了List,RandomAccess,Cloneable與Serializable接口
二ArrayList類一些重要屬性:
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
從這裏可以看到ArrayList全部操作是基於Object[]數據結構的,即ArrayList底層是基於數組實現的,因此具備較好的隨機訪問的能力三ArrayList內部實現原理:我們先來看一下ArrayList的構造器
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);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
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的時候,其內部處理過程與LinkedList非常類似,即先將集合轉化爲數組,然後將元素複製到自己內部定義的數組elementData數組中。返回的若不是Object[]將調用Arrays.copyOf方法將其轉爲Object[]。Arrays.copyOf代碼如下:
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
四ArrayList一些重要的函數:
1add(E e)與add(int index,E element)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
其中add(E e)表示在ArrayList的末尾添加一個元素,而add(int index,E element)表示在index處添加一個元素,其中爲了防止當數組中的容量不夠的情況,會調用ensureCapacityInternal(size + 1); 函數用來確保向數組中添加元素的時候數組大小始終足夠。代碼如下;
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
而在該函數中調用了ensureExplicitCapacity(int minCapacity)函數,我們來看一下其源碼:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)// 超出了數組可容納的長度,則進行動態擴展,即調用grow函數
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//將oldCapacity除以2在加上自己,即新的容量爲原來的1.5倍
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);
}
綜上,當調用add()添加元素時,add方法先調用ensureCapacityInternal方法增加自身容量(注意此時僅僅是minCapicity的值加1,而數組內存還未擴容),本質上是通過grow()函數來完成的,在調用grow()函數時,先求出原數組的長度記爲oldCapacity,然後將該值更新爲oldCapacity+oldCapacity>>1即新的容量爲原來容量的1.5倍.,將其記爲newCapacity,如果擴容後的容量超出了數組可容納的最大長度MAX_ARRAY_SIZE,則調用hugeCapacity()將其返回值 Integer.MAX_VALUE作爲新的容量,然後調用系統的arraycopy函數將原數組的內容複製到新數組中返回。
即add()方法中存在擴容機制,當通過add()方法添加一個元素時,會先調用ensureCapacityInternal方法來確保數組內存始終足夠,本質上是通過grow函數來完成的,在grow()函數中會將原數組的長度更改爲其原來的1.5倍,如果更改後的數組容量比傳入的minCapacity小,則將更改後的容量更新爲minCapacity(該值是期望的數組的最小容量,如當已存在1個元素然後添加一個元素則minCapacity的值爲2即能容納添加進去的元素的最小容量,擴容後的值newCapacity在絕大多數情況下比它大,但可能比它小,因爲上述擴容機制採用的是擴大爲1.5倍,而採用的是整除,如1+1/2=1,而minCapacity=size+1=2,此時newCapacity<minCapacity)
2addAll(Collection<? extends E> c) 與addAll(int index, Collection<? extends E> c)
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;
}
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;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
可以看到,addAll()與add()方法幾乎完全相同,只不過存在一個將集合轉化爲數組的過程,這一點與LinkedList非常類似,然後接下來的操作與add操作完全相同,即
調用ensureCapacityInternal擴容,如果插入位置不是Arraylist的末尾,則調用系統的arraycopy函數將索引處及其後的元素後移一位在index處插入該元素。處理完畢後將臨時數組a中的元素arraycopy到elementData中。
3Object[] toArray()
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
該函數的作用就是返回一個包含list中所有元素的數組,具體實現過程如下:
如果傳入數組的長度length小於size,返回一個新的數組,大小爲size,類型與傳入數組相同;
否則將elementData的元素全部複製到傳入的數組a中,並返回a;
若length大於size,除了複製elementData外,還將把返回數組的第size個元素置爲空。
4void sort(Comparator<? super E> c)
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++;
}
可以看到該sort函數是基於Comparator<? super E> c接口的,即待排序的元素必須實現Comparator接口,其實不僅僅是ArrayList,java集合框架中的全部集合的排序都是基於
Comparator接口的。
五總結:
1ArrayList的內部是基於Object[]類型的數組實現的,支持下標訪問,因此具備較高的隨機訪問速度。
2當用一個集合作爲參數來構造一個ArrayList時,其內部是是先將該集合轉化爲數組,這一點與LinkedList非常類似,與HashMap一樣,ArrayList也存在擴容機制,即調用ensureCapacityInternal擴容
3可以看到ArrayList中的方法都未使用synchronized關鍵字修飾,即ArrayList是非同步的。
4ArrayList允許重複元素存在,因爲在add元素的過程中不存在HashMap中put時判重替換的過程,只是進行簡單的插入操作。