ArrayList 是一個數組隊列,至關於 動態數組。與Java中的數組相比,它的容量能動態增加。它繼承於AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些接口。java
ArrayList 繼承了AbstractList,實現了List。它是一個數組隊列,提供了相關的添加、刪除、修改、遍歷等功能。 ArrayList 實現了RandmoAccess接口,即提供了隨機訪問功能。RandmoAccess是java中用來被List實現,爲List提供快速訪問功能的。在ArrayList中,咱們便可以經過元素的序號快速獲取元素對象;這就是快速隨機訪問。稍後,咱們會比較List的「快速隨機訪問」和「經過Iterator迭代器訪問」的效率。數組
ArrayList 實現了Cloneable接口,即覆蓋了函數clone(),能被克隆。安全
ArrayList 實現java.io.Serializable接口,這意味着ArrayList支持序列化,能經過序列化去傳輸。bash
和Vector不一樣,ArrayList中的操做不是線程安全的!因此,建議在單線程中才使用ArrayList,而在多線程中能夠選擇Vector或者CopyOnWriteArrayList。數據結構
ArrayList 有如下三種初始化的方法,此爲1.8.0的源碼,和以前的源碼有所區別,在以前的源碼中,默認狀況下,初始容量爲10,而在1.8的源碼中,默認狀況下則爲多線程
/**
* 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 = {};
複製代碼
/**
* 指定一個初始容量的構造方法
*
* @param ArrayList的初始容量
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
//若是初始容量>0 ,則以指定的值爲初始容量
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//若是指定的值爲0,則直接賦值爲空數組
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
*在此方法中則默認賦值一個DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空數組,此數組和EMPTY_ELEMENTDATA 區分開來,用來在添加第一個元素的時候,肯定要擴容多少
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
*構造一個包含指定元素的list,這些元素的是按照Collection的迭代器返回的順序排列的
*
* @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) {
//若是是非空的集合,則直接拷貝進入當前數組
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 若是傳入的數據爲空,則使用默認的空數組代替
this.elementData = EMPTY_ELEMENTDATA;
}
}
複製代碼
##三、增刪改查dom
/**
* 直接在集合的末尾添加一個元素
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
複製代碼
add方法分紅了兩步進行,函數
從代碼中能夠看出,進行添加數據操做的時候,會先判斷當前底層的數組是不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,這個數組在構造方法 ArrayList() 中被賦值,用來區別於EMPTY_ELEMENTDATA ,在第一次進行數組操做的時候進行判斷,從源碼能夠看出,會和默認的容量10進行比較,取較大的值,相對於以前直接 賦值爲 10 的容量,我的以爲有兩點優點:一、初始化的時候不會直接開闢空間 二、減小了一次擴容操做;效率上有必定的提高,設計仍是比較巧妙的源碼分析
private void ensureCapacityInternal(int minCapacity) {
//若是調用的是 參數爲空的構造方法,第一次操做的時候,就判斷一下,
//若是此時請求的容量大於默認初始容量10,取請求的容量,不然爲默認容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//明確容量
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 若是請求的最小容量大於數組長度,則進行一次擴容,以儲存全部數據
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//增長容量,以確保能容納mincapacity指定的元素
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//擴容,新的容量爲原來的 1.5倍
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:
//將老數據copy到新的數組中
elementData = Arrays.copyOf(elementData, newCapacity);
}
複製代碼
####add 的其餘方法 從源碼能夠很明確的看出來,和add(E e)很是接近,僅在copy數據的時候有所差異性能
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
ensureCapacityInternal(size + 1); // Increments modCount!!
//和add(E e)方法的區別僅這一行代碼,擴容以後,將老數據在從指定位置(index)開始
//以後的數據都向後移動一位,而後將index位置的賦值爲 e
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
//和add 方法相似,區別在於將傳入的集合轉化成數組,而後總體位移copy
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
//擴容判斷
ensureCapacityInternal(size + numNew); // Increments modCount
//將傳入的數據copy進入數組
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
複製代碼
移出數據有 remove(int index) remove(Object o) ,兩個方法本質都是根據index,直接利用數組的下標刪除指定數據,同時會將數據進行位移,填補空缺
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
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;
}
複製代碼
ArrayList底層是數組,所以查找就很是簡單,直接根據數組下標找到數據
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}
複製代碼
更改也是很是簡單,直接根據數組下標更改數據
public E set(int index, E element) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
複製代碼
從上面的源碼能夠很明顯的看出,ArrayList的增刪改查操做實質上就是對底層數組的操做,新增的時候,都須要對數組進行擴容+copy操做,刪除也須要對數組進行copy,因此ArrayList的增長和刪除效率會很是低,可是相對的,得利於底層的數組結果,在進行查找和更改操做的時候,能夠根據下標直接進行操做,只有O(1)的複雜度,所以在查找需求比較頻繁的操做中,可使用ArrayList,極大的增長操做效率,可是在增刪比較頻繁的時候,就須要考慮其餘的數據結構了; 同時,合理的使用ArrayList的構造方法,例如在初始化的時候,若是已經知道當前數據的大小,能夠直接使用ArrayList(int initialCapacity) 構造方法指定初始容量,這樣能夠避免在添加數據的時候頻繁擴容下降性能,同時也能夠避免1.5倍擴容機制形成的空間浪費