一、ArrayList的底層實現是一個數組,數組裏元素類型是Object類型,可以存放所有類型的數據,對ArrayList類的實例的所有操作都是基於數組的操作。
ArrayList的數據結構如下:
二、ArrayList源碼分析
2.1 類的繼承關係
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
注:ArrayList繼承AbstractList抽象父類,實現了List接口(規範了List的操作規範),實現了RandomAccess接口(可隨機訪問),
實現了Cloneable(可拷貝),實現了Serializable接口(可序列化)。
2.2 類的屬性
//版本號
private static final long serialVersionUID = 8683452581122892189L;
//默認初始容量
private static final int DEFAULT_CAPACITY = 10;
//空對象數組(用於空實例共享空數組實例)
private static final Object[] EMPTY_ELEMENTDATA = {};
//默認空對象數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放元素的數組
transient Object[] elementData; // non-private to simplify nested class access
//實際元素大小,對象創建時會初始化爲0
private int size;
//最大數組容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
注:核心屬性爲elementDate數組,類型爲Object[],用於存放實際元素,並且不會被序列化。
2.3 類的構造函數
1. ArrayList(int)型構造函數
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);
}
}
2. ArrayList()型構造函數
public ArrayList() {
//無參構造函數,默認數組元素爲空
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
注:當未指定初始化大小時,給elementData賦值爲空數組集合。
3.
ArrayList(Collection<? extends E>)型構造函數
public ArrayList(Collection<? extends E> c) { //參數爲一個集合
elementData = c.toArray();//將集合轉化爲數組
if ((size = elementData.length) != 0) {//參數傳遞過來的集合是非空的
if (elementData.getClass() != Object[].class) //判斷是否轉化爲Object數組
//如果沒有轉化成功,就將集合裏的元素複製到一個Object數組裏
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else { //當集合爲空時,初始化一個空數組
this.elementData = EMPTY_ELEMENTDATA;
}
}
注:當傳遞的參數爲集合類型時,會將集合 類型轉化爲數組類型,並賦值給elementDate。
2.4類的縮小容量機制
//當數組容量大小大於實際元素個數時,可以調用此方法節約空間
public void trimToSize() {
modCount++;
//size爲實際元素個數
//elementData.length爲數組容量
if (size < elementData.length) {
//證明數組中有空元素,進行縮小容量操作
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
2.5 增加元素操作
1.外部調用的接口(返回增加成功或失敗)
public boolean add(E e) { //添加一個值,首先會確保容量
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
上面是添加一個值,一般添加在數組的末尾,下面這段是在index位置添加一個元素
public void add(int index, E element) {
rangeCheckForAdd(index); //檢查如果index越界或小於0,拋出異常
ensureCapacityInternal(size + 1); // 確保容量,保證不會溢出
//public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
//src:源數組; srcPos:源數組要複製的起始位置; dest:目的數組; destPos:目的數組放置的起始位置; length:複製的長度
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;//將當前元素放在index數組上 size++;
}
2.
add 方法裏面調用了ensureCapacityInternal(size + 1)方法
//在添加元素時,確保數組的容量
private void ensureCapacityInternal(int minCapacity) {
//當elementData爲空數組時,證明是第一次添加元素
//將參數和默認初始容量中的較大值設爲數組容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//不是第一次添加時,當參數大於數組容量時,增加數組容量
ensureExplicitCapacity(minCapacity);
}
3.上述ensureCapacityInternal方法中又調用了ensureExplicitCapacity(minCapacity)
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//參數容量大於數組容量,就是數組不夠用了,調用grow()函數進行擴容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
注:grow纔會對數組進行真正的擴容,ensureCapacityInternal、ensureExplicitCapacity都只是過程。2.5 數組擴容 grow()源碼如下
private void grow(int minCapacity) {
int oldCapacity = elementData.length;//舊容量
int newCapacity = oldCapacity + (oldCapacity >> 1);//新容量爲舊容量的1.5倍
//比較新容量和參數值,取出較大的值作爲將要擴容的容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果將要擴容的容量大於最大可以允許的值,檢查是否會溢出,由hugeCapacity檢查
//如果溢出,拋出異常,如果沒溢出,將要擴容的容量爲最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//按照行容量進行擴容
elementData = Arrays.copyOf(elementData, newCapacity);
}
注:正常情況下會擴容1.5倍,特殊情況下(新擴展數組大小已經達到了最大值)則只取最大值。當我們調用add方法時,實際上的函數調用如下
2.6 grow函數對hugeCapacity進行了調用
//將要擴容的容量大於最大值,調用此函數判斷是否會溢出
//溢出則拋異常,沒有溢出的話將要擴容的容量就爲最大值
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 溢出
throw new OutOfMemoryError(); //拋異常
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
2.7 返回數組大小
public int size() {
return size;
}
2.8
判斷數組是否爲空
public boolean isEmpty() {
return size == 0;
}
2.9判斷數組是否包含此元素
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
2.10
找出當前值在數組中首次出現的位置 indexOf(Object o)
//找出當前值在數組中首次出現的位置,會根據當前值是否爲null使用不同的方式去判斷
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
2.11
public int lastIndexOf(Object o) ,找出當前元素在數組中最後一次出現的位置
//找出當前值在數組中最後一次出現的位置,不存在就返回-1
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
2.11
克隆
//返回副本,元素本身沒有被賦值,如果數組中途發生改變就拋出異常
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
2.12
toArray() 將數組轉換爲Object數組
//轉換爲Object數組,使用Arrays.copyOf方法(相當於複製)
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
2.13
通過下標獲得數組元素的值
public E get(int index) {
rangeCheck(index); //對下標進行越界的檢測
return elementData(index);
}
2.14
在固定的下標上設置元素
public E set(int index, E element) {
rangeCheck(index);//對下標進行越界的檢測
E oldValue = elementData(index);//取出index位置上的值賦給oldValue
elementData[index] = element;//將當前值放到index位置上
return oldValue; //返回oldValue
}
2.15
public boolean remove(Object o) 刪除指定元素首次出現的位置,只返回是否刪除成功,根據元素是否爲null有兩種不同的方式
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;
}
注:藉助fastRemove(index) 函數刪除元素,之所以叫快速刪除,是因爲不需要檢查元素下標是否符合範圍,也不需要返回值,只是內部使用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;
}
2.17
public E remove(int index) 刪除指定位置的值,並且會檢查下標和返回被刪除的值
public E remove(int index) {
rangeCheck(index);//檢查元素下標
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
return oldValue;
}
2.18 public void clear() 清空數組,將每一個值設爲null,方便垃圾回收
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
2.19
public boolean addAll(Collection<? extends E> c) 將集合的元素添加到末端,如果集合爲空返回falsepublic boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();//將集合轉化爲Object類型的數組
int numNew = a.length;//數組的長度
ensureCapacityInternal(size + numNew); //確保數組的容量
System.arraycopy(a, 0, elementData, size, numNew); //添加數據,實質是複製
size += numNew;
return numNew != 0;
}
2.20 public boolean addAll(int index, Collection<? extends E> c) 功能如上,只是從指定位置開始添加 public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray(); //將集合轉化爲Object數組
int numNew = a.length; // 數組長度
ensureCapacityInternal(size + numNew); //確保容量
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;
}
2.21protected void 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);
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
源碼我們就介紹到這裏,下面說一些總結性的東西三、常見問題
3.1 爲什麼ArrayList不適合刪除和插入操作?
在ArrayList中在刪除和插入時會對數組進行移動,通過調用System.arrayCopy來複制數組,所以效率低下。
3.2 在ArrayList中提供了兩種迭代器Iterator和ListIterator,有什麼區別?
從上圖可以看出,ListIterator相比Iterator來說,增加了增,刪,設定元素以及向前或向後遍歷元素的操作。
3.3快速失敗機制 fast-fail
容器的“快速失敗”機制是Java集合中的一種錯誤檢測的機制,當多個線程對集合進行結構上的改變的操作時,有可能會產生fail-fast機制。它是
一種迭代器檢測bug的機制,比如:我們有線程A和線程B,線程A通過Iterator訪問集合中的元素,某個時刻,線程B修改了集合中的結構,那麼程序
就會拋出ConcurrentModificationException。
實現:在add,remove,next中都調用了此方法,判斷modCount和expectedModCount是否相等來拋出異常
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
在迭代器中expectedModCount 是這樣定義的,int expectedModCount = modCount; 這個值是不會變化的,所以使用迭代器遍歷整個ArrayList時,我們只需要判斷那些可能會讓 ModCount 改變的方法,從而知道了集合是否發生了結構上的變化。