public interface RandomAccess {
}
複製代碼
打開源碼一看,這是一個空接口,即標記接口,這個接口的做用就是使ArrayList具有快速隨機訪問的功能。什麼是隨機訪問?就是隨機訪問ArrayList中的任何一個元素。那ArrayList是如何快速隨機訪問任何一個元素的呢,咱們稍後再說明。數組
咱們再來看下父類AbstractList,從上面的圖1能夠看出,AbstractList是一個抽象類,也一樣實現了List接口,咱們能夠猜測,AbstractList是對List的具體實現類進行的抽象化的提取。因爲篇幅緣由,代碼爲就不貼了。bash
接下來咱們就看ArrayList類代碼的具體實現,首先咱們看到ArrayList類定義了4個常量,2個實例變量,分別是:框架
//默認容量
private static final int DEFAULT_CAPACITY = 10;
//空元素數組
private static final Object[] EMPTY_ELEMENTDATA = {};
//默認容量對應的空元素數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//元素,被transient關鍵字修飾,無需序列化
transient Object[] elementData; // non-private to simplify nested class access
//長度
private int size;
//最大長度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
複製代碼
從以上源碼咱們能夠猜測,ArrayList是經過內部維護一個的Object對象數組實現的,且默認的ArrayList容量是10。
複製代碼
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//指定初始化容量,若是指定容量大於0,則設置元素數組爲指定長度的對象數組;
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//若是指定容量等於0,那麼設置元素數組爲空數組;
this.elementData = EMPTY_ELEMENTDATA;
} else {
//若是指定容易小於0,則拋出異常;
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) {
// 若是元素數組長度不爲0,且數組對象不是Object,則將數組元素轉爲Object對象;
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 若是元素數組長度爲空,則替換成常量空數組;
this.elementData = EMPTY_ELEMENTDATA;
}
}
複製代碼
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
複製代碼
很明顯,咱們能看得出,get方法純粹就是ArrayList內部維護的元素數組操做,經過數組下標取值,很是簡單。至此,回顧一開始的問題,ArrayList爲何讀取快?由於是數組下標直接讀取操做,複雜度是O(1)。dom
public boolean add(E e) {
//將數組長度+1做爲add操做須要的最小容量傳入方法中
ensureCapacityInternal(size + 1);
elementData[size++] = e; //將元素添加到數組末端,所以主要邏輯在前一句代碼
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//根據當前元素數組長度,和add操做須要的最小容量作比較,肯定操做須要的最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//該步驟確保元素數組知足操做所需的最小容量
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 若所需的最小容量大於當前元素數組長度,則對元素數組進行擴容處理
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//擴容操做
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 新容量=舊容量+舊容量/2;
int newCapacity = oldCapacity + (oldCapacity >> 1);
//若第一步擴容後,新容量仍是不知足所需最小容量,則將新容量設置爲所需最小容量;
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 若新容量比最大的容許容量還大,那麼須要從新計算新容量;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 計算完畢,將元素複製到新容量的數組上
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;
}
複製代碼
以上無參數的add方法源碼,其實帶參數的add方法跟無參數的基本同樣,只是增長了一個指定位置到數組末端元素複製的方法,以下:函數
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++;
}
複製代碼
能看得出,ArrayList的無參數add方法,須要通過兩個大步驟,第一步是計算容量,第二步是若是擴容了,須要將原來數組內的元素,全量複製到新的數組上。至此,咱們又能回答一開始的問題,ArrayList爲何寫操做慢?由於ArrayList寫操做會對部分甚至所有元素進行移動操做,同理,對於set、remove等寫操做的方法,總體上都是這樣的操做,因此寫操做效率低。ui