1、ArrayList類的說明
本篇分析ArrayList的源碼,在分析之前先跟大家談一談數組。數組可能是我們最早接觸到的數據結構之一,它是在內存中劃分出一塊連續的地址空間用來進行元素的存儲,由於它直接操作內存,所以數組的性能要比集合類更好一些,這是使用數組的一大優勢。但是我們知道數組存在致命的缺陷,就是在初始化時必須指定數組大小,並且在後續操作中不能再更改數組的大小。在實際情況中我們遇到更多的是一開始並不知道要存放多少元素,而是希望容器能夠自動的擴展它自身的容量以便能夠存放更多的元素。ArrayList就能夠很好的滿足這樣的需求,它能夠自動擴展大小以適應存儲元素的不斷增加。它的底層是基於數組實現的,因此它具有數組的一些特點,例如查找修改快而插入刪除慢。本篇我們將深入源碼看看它是怎樣對數組進行封裝的。首先看看它的成員變量和三個主要的構造器。-------轉自 https://www.cnblogs.com/liuyun1995/p/8286829.html
1.1 解釋說明
* 可變數組(ArrayList)實現了List的接口. 實現了所有List的操作,和允許所有類型的元素,包括
* 空值,除了實現List接口之外,這個類還提供了操作數組大小(size)這個作爲一個元素
* 存儲在數組內部。這個數組粗略(roughly)的等同於Vecotr,除了ArrayList不保證線程
* 安全,(當然了還有擴容的方式,ArrayList是1.5倍,Vertor是2倍)
*
*
* ArrayList中的size()計算元素個數,isEmpty()判斷數組是否爲空,get()獲取元素,set()
* 修改元素,iterator()迭代這些操作都是常量O(1)的時間複雜度。而add()增加操作是
* 平均時間複雜度(amortized constant time),換言之,增加一個元素需要O(n)時間複雜度
* 一般來說,所有的其他操作都是線性的時間複雜度。這個算法的常量因子(constant factor)
* 低於LinkedList的。
*
*
* 每一個ArrayList都有一個容量。這個容量時數組的用於存儲元素於數組的大小。它總是
* 至少與數組元素個數大小相等。當元素被加入ArrayList,這個容量是會增長的。這個容量
* 擴容的方法的平均時間複雜度沒有比增加一個元素還複雜。
*
*
* 可以應用ensureCapacity()方法來對數組擴容當要加入大量的元素的時候,這樣可以減少
* 因爲增加元素而重分配的次數。(也就是說最好一開始確定好所需要的容量大小)
*
*
* 需要注意的是這個方法不是同步的方法,需要同步的應用(copyonwriteArrayList),如果多個
* 線程同時操作ArrayList實例和至少有一個線程修改list的結構,必須在外部加同步操作。關於
* 結構性操作可以看前面的HashMap的介紹。這個同步操作通常是壓縮在某些對象頭上面。(synchronized
* 就是存儲在對象頭上面。)
*
*
* 如果對象頭不存在這樣的對象,這個列表應該使用{@link Collections#synchronizedList Collections.synchronizedList}工具
* 來封裝,這個操作最好是在創建List之前完成,防止非同步的操作。
* List list = Collections.synchronizedList(new ArrayList(...));
* 但是一般不用這個方法,而是用JUC包下的CopyOnWriteArrayList更加高效。
*
*
* 快速失敗機制(...好像在集合容器下面都有這個機制來拋出異常...)
* 當一個list被多個線程成同時修改的時候會拋出異常。但是不能用來保證線程安全。
* 所以在多線程環境下,還是要自己加鎖或者採用JUC包下面的方法來保證線程安全,
* 而不能依靠fail-fast機制拋出異常,這個方法只是用來檢測bug。
1.2 數據結構
ArrayList大概可以用這張圖來描述,主要是一個ArrayList實例,裏面封裝着一個elementdata數組。其中有一個size字段,每次增加一個元素的時候就加1,刪除就減1。這樣當需要獲取數組長度的時候,就可以在時間複雜度爲O(1)下。
2、方法字段
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默認初始容量爲10,Vector也是10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 共享空對象數組
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
-* 默認的空對象數組,以此來區分當添加第一個元素的時候,數組是怎麼樣膨脹的
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存儲ArrayList元素的數組緩衝區。
* ArrayList的容量是這個數組緩衝區的長度。任何
* 使用elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA清空ArrayList
* 將在添加第一個元素時擴展爲DEFAULT_CAPACITY。
*/
//內部封裝的數組
transient Object[] elementData; // 非私有來簡化嵌套類的訪問
/**
* ArrayList中的元素個數
*/
private int size;
.
.
.
/**
* 能分配的最大數組大小,因爲有些數組會在頭分配一下信息,
* 所以要注意這裏的最大大小是Integer.MAX_VALUE - 8。
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
.
.
.
/*
* 多線程環境下,當涉及到結構性修改(add,remove)時,用modCount來判斷是否會有多個線程同時修改,
* 當modeCount不是期望值時候,拋出異常
* fail-fast
*/
protected transient int modCount = 0;
}
ArrayList的方法字段比較少,主要的是 private static final int DEFAULT_CAPACITY = 10;
,初始化默認爲10的容量大小。還有兩個默認的空數組。另外兩個就是前面提到。
3、主要方法
構造函數
/**
* 構造具有初始容量的空數組
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];//當給出初始容量且初始容量大於0時,用初始容量來初始化
} else if (initialCapacity == 0) {//當給出的默認容量爲0時,則構造一個空數組
this.elementData = EMPTY_ELEMENTDATA;
} else {//當給出的是小於0的,則拋出異常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 如果沒有給初始容量,則初始化一個大小爲10的默認容量
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 當傳入一個數組的時候,則將這個數組包裝成ArrayList,並返回這個數組的迭代順序
*/
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);//將數組賦值到ArrayList
} else {
// 如果傳入的是一個空數組,則構造一個空的對象
this.elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList的構造函數主要有3種,一種是傳入初始容量的,採用默認的,還有就是傳入一個數組,以這個數組的大小爲容量,裏面的元素進行初始化。
trimToSize()
/**
*將ArrayList大小修改爲元素個數的大小。
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
/**
* ArrayList擴容
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//獲取原始數組大小
int newCapacity = oldCapacity + (oldCapacity >> 1);//關鍵是這一句,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);//拷貝原來的數組到新的數組
}
size(),isEmpty()
/**
* 返回當前ArrayList中的元素個數
*
*/
public int size() {
return size;
}
/**
* 返回是否包含元素
*
*/
public boolean isEmpty() {
return size == 0;
}
indexOf(Object o)
/**
* 返回第一次出現該元素的下標,如果不存在則返回-1
*/
public int indexOf(Object o) {
if (o == null) {//如果對象爲空,因爲ArrayList允許空對象,所以查找到第一個爲空的
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;//找不到,返回-1
}
lastIndexOf(Object o)
/**
* 返回最後一次出現的index,不存在則返回-1
* 與indexOf一樣,只不過是從後往前面掃描
*/
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;
}
get() set() add(E e)
// 返回指定位置的元素
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
/**
* 獲取指定下標的元素
*/
public E get(int index) {
rangeCheck(index);//檢查下標是否合法
return elementData(index);//返回相應下標的元素
}
/**
* 設置指定下標的元素爲新值
*/
public E set(int index, E element) {
rangeCheck(index);//檢查下標是否合法
E oldValue = elementData(index);//獲取相應下標的舊值
elementData[index] = element;//設置爲新值
return oldValue;//返回舊值
}
/**
*增加元素,沒有指定下標則在最後添加
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 涉及到結構性修改,modcount++,
elementData[size++] = e;//在末尾追加元素
return true;
}
add(int index, E element)
/**
* 插入元素至指定位置
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // 涉及到結構性修改,modcount++
System.arraycopy(elementData, index, elementData, index + 1,
size - index);//元素後移,實際是複製到另外一個數組中,只不過這裏的數組是原數組
elementData[index] = element;//添加元素
size++;//數組中元素個數加1
}
remove()
/**
* 刪除指定下標元素
*/
public E remove(int index) {
rangeCheck(index);//檢查數組下標是否合法
modCount++;// 涉及到結構性修改,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;
}
/**
* 刪除第一次出現的元素,成功則返回true,否則返回false
*/
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;
}
/*
* 刪除最後一次出現的元素
*/
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; // clear to let GC do its work
}
4、總結
每次添加元素前會調用ensureCapacityInternal這個方法進行集合容量檢查。在這個方法內部會檢查當前集合的內部數組是否還是個空數組,如果是就新建默認大小爲10的Object數組。如果不是則證明當前集合已經被初始化過,那麼就調用ensureExplicitCapacity方法檢查當前數組的容量是否滿足這個最小所需容量,不滿足的話就調用grow方法進行擴容。在grow方法內部可以看到,每次擴容都是增加原來數組長度的一半,擴容實際上是新建一個容量更大的數組,將原先數組的元素全部複製到新的數組上,然後再拋棄原先的數組轉而使用新的數組。至此,我們對ArrayList中比較常用的方法做了分析,其中有些值得注意的要點:
- ArrayList底層實現是基於數組的,因此對指定下標的查找和修改比較快,但是刪除和插入操作比較慢。
- 構造ArrayList時儘量指定容量,減少擴容時帶來的數組複製操作,如果不知道大小可以賦值爲默認容量10。
- 每次添加元素之前會檢查是否需要擴容,每次擴容都是增加原有容量的一半。
- 每次對下標的操作都會進行安全性檢查,如果出現數組越界就立即拋出異常。
- ArrayList的所有方法都沒有進行同步,因此它不是線程安全的。
- 以上分析基於JDK1.8,其他版本會有些出入,因此不能一概而論。