ArrayList 源碼分析
- ArrayList簡介
- ArrayList核心源碼
- ArrayList源碼分析
- System.arraycopy()和Arrays.copyOf()方法
- 兩者聯系與區別
- ArrayList核心擴容技術
- 內部類
- System.arraycopy()和Arrays.copyOf()方法
- ArrayList經典Demo
ArrayList簡介
ArrayList 的底層是數組隊列,相當於動態數組。與 Java 中的數組相比,它的容量能動態增長。在添加大量元素前,應用程序可以使用ensureCapacity
操作來增加 ArrayList 實例的容量。這可以減少遞增式再分配的數量。
它繼承於 AbstractList,實現了 List, RandomAccess
在我們學數據結構的時候就知道了線性表的順序存儲,插入刪除元素的時間復雜度為O(n),求表長以及增加元素,取第 i 元素的時間復雜度為O(1)
ArrayList 繼承了AbstractList,實現了List。它是一個數組隊列,提供了相關的添加、刪除、修改、遍歷等功能。
ArrayList 實現了RandomAccess 接口,即提供了隨機訪問功能。RandomAccess 是 Java 中用來被 List 實現,為 List 提供快速訪問功能的。在 ArrayList 中,我們即可以通過元素的序號快速獲取元素對象,這就是快速隨機訪問。
ArrayList 實現了Cloneable 接口,即覆蓋了函數 clone(),能被克隆。
ArrayList 實現java.io.Serializable 接口,這意味著ArrayList支持序列化,能通過序列化去傳輸。
和 Vector 不同,ArrayList 中的操作不是線程安全的!所以,建議在單線程中才使用 ArrayList,而在多線程中可以選擇 Vector 或者 CopyOnWriteArrayList。
ArrayList核心源碼
package java.util; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** * 默認初始容量大小 */ private static final int DEFAULT_CAPACITY = 10; /** * 空數組(用於空實例)。 */ private static final Object[] EMPTY_ELEMENTDATA = {}; //用於默認大小空實例的共享空數組實例。 //我們把它從EMPTY_ELEMENTDATA數組中區分出來,以知道在添加第一個元素時容量需要增加多少。 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 保存ArrayList數據的數組 */ transient Object[] elementData; // non-private to simplify nested class access /** * ArrayList 所包含的元素個數 */ private int size; /** * 帶初始容量參數的構造函數。(用戶自己指定容量) */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { //創建initialCapacity大小的數組 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //創建空數組 this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** *默認構造函數,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 為0.初始化為10,也就是說初始其實是空數組 當添加第一個元素的時候數組容量才變成10 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 構造一個包含指定集合的元素的列表,按照它們由集合的叠代器返回的順序。 */ public ArrayList(Collection<? extends E> c) { // elementData = c.toArray(); //如果指定集合元素個數不為0 if ((size = elementData.length) != 0) { // c.toArray 可能返回的不是Object類型的數組所以加上下面的語句用於判斷, //這裏用到了反射裏面的getClass()方法 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 用空數組代替 this.elementData = EMPTY_ELEMENTDATA; } } /** * 修改這個ArrayList實例的容量是列表的當前大小。 應用程序可以使用此操作來最小化ArrayList實例的存儲。 */ public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } } //下面是ArrayList的擴容機制 //ArrayList的擴容機制提高了性能,如果每次只擴充一個, //那麽頻繁的插入會導致頻繁的拷貝,降低性能,而ArrayList的擴容機制避免了這種情況。 /** * 如有必要,增加此ArrayList實例的容量,以確保它至少能容納元素的數量 * @param minCapacity 所需的最小容量 */ public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It's already // supposed to be at default size. : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } //得到最小擴容量 private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 獲取默認的容量和傳入參數的較大值 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } //判斷是否需要擴容 private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) //調用grow方法進行擴容,調用此方法代表已經開始擴容了 grow(minCapacity); } /** * 要分配的最大數組大小 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * ArrayList擴容的核心方法。 */ private void grow(int minCapacity) { // oldCapacity為舊容量,newCapacity為新容量 int oldCapacity = elementData.length; //將oldCapacity 右移一位,其效果相當於oldCapacity /2, //我們知道位運算的速度遠遠快於整除運算,整句運算式的結果就是將新容量更新為舊容量的1.5倍, int newCapacity = oldCapacity + (oldCapacity >> 1); //然後檢查新容量是否大於最小需要容量,若還是小於最小需要容量,那麽就把最小需要容量當作數組的新容量, if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //再檢查新容量是否超出了ArrayList所定義的最大容量, //若超出了,則調用hugeCapacity()來比較minCapacity和 MAX_ARRAY_SIZE, //如果minCapacity大於最大容量,則新容量則為ArrayList定義的最大容量,否則,新容量大小則為 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); } //比較minCapacity和 MAX_ARRAY_SIZE private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } /** *返回此列表中的元素數。 */ public int size() { return size; } /** * 如果此列表不包含元素,則返回 true 。 */ public boolean isEmpty() { //註意=和==的區別 return size == 0; } /** * 如果此列表包含指定的元素,則返回true 。 */ public boolean contains(Object o) { //indexOf()方法:返回此列表中指定元素的首次出現的索引,如果此列表不包含此元素,則為-1 return indexOf(o) >= 0; } /** *返回此列表中指定元素的首次出現的索引,如果此列表不包含此元素,則為-1 */ 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++) //equals()方法比較 if (o.equals(elementData[i])) return i; } return -1; } /** * 返回此列表中指定元素的最後一次出現的索引,如果此列表不包含元素,則返回-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; } /** * 返回此ArrayList實例的淺拷貝。 (元素本身不被復制。) */ public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); //Arrays.copyOf功能是實現數組的復制,返回復制後的數組。參數是被復制的數組和復制的長度 v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // 這不應該發生,因為我們是可以克隆的 throw new InternalError(e); } } /** *以正確的順序(從第一個到最後一個元素)返回一個包含此列表中所有元素的數組。 *返回的數組將是“安全的”,因為該列表不保留對它的引用。 (換句話說,這個方法必須分配一個新的數組)。 *因此,調用者可以自由地修改返回的數組。 此方法充當基於陣列和基於集合的API之間的橋梁。 */ public Object[] toArray() { return Arrays.copyOf(elementData, size); } /** * 以正確的順序返回一個包含此列表中所有元素的數組(從第一個到最後一個元素); *返回的數組的運行時類型是指定數組的運行時類型。 如果列表適合指定的數組,則返回其中。 *否則,將為指定數組的運行時類型和此列表的大小分配一個新數組。 *如果列表適用於指定的數組,其余空間(即數組的列表數量多於此元素),則緊跟在集合結束後的數組中的元素設置為null 。 *(這僅在調用者知道列表不包含任何空元素的情況下才能確定列表的長度。) */ @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { if (a.length < size) // 新建一個運行時類型的數組,但是ArrayList數組的內容 return (T[]) Arrays.copyOf(elementData, size, a.getClass()); //調用System提供的arraycopy()方法實現數組之間的復制 System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } // Positional Access Operations @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) { //對index進行界限檢查 rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; //返回原來在這個位置的元素 return oldValue; } /** * 將指定的元素追加到此列表的末尾。 */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! //這裏看到ArrayList添加元素的實質就相當於為數組賦值 elementData[size++] = e; return true; } /** * 在此列表中的指定位置插入指定的元素。 *先調用 rangeCheckForAdd 對index進行界限檢查;然後調用 ensureCapacityInternal 方法保證capacity足夠大; *再將從index開始之後的所有成員後移一個位置;將element插入index位置;最後size加1。 */ public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! //arraycopy()這個實現數組之間復制的方法一定要看一下,下面就用到了arraycopy()方法實現數組自己復制自己 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } /** * 刪除該列表中指定位置的元素。 將任何後續元素移動到左側(從其索引中減去一個元素)。 */ 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; // clear to let GC do its work //從列表中刪除的元素 return oldValue; } /** * 從列表中刪除指定元素的第一個出現(如果存在)。 如果列表不包含該元素,則它不會更改。 *返回true,如果此列表包含指定的元素 */ 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 remove method that skips bounds checking and does not * return the value removed. */ 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 } /** * 從列表中刪除所有元素。 */ public void clear() { modCount++; // 把數組中所有的元素的值設為null for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } /** * 按指定集合的Iterator返回的順序將指定集合中的所有元素追加到此列表的末尾。 */ 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; } /** * 從此列表中刪除所有索引為fromIndex (含)和toIndex之間的元素。 *將任何後續元素移動到左側(減少其索引)。 */ protected void removeRange(int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // clear to let GC do its work int newSize = size - (toIndex-fromIndex); for (int i = newSize; i < size; i++) { elementData[i] = null; } size = newSize; } /** * 檢查給定的索引是否在範圍內。 */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * add和addAll使用的rangeCheck的一個版本 */ private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * 返回IndexOutOfBoundsException細節信息 */ private String outOfBoundsMsg(int index) { return "Index: "+index+", Size: "+size; } /** * 從此列表中刪除指定集合中包含的所有元素。 */ public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); //如果此列表被修改則返回true return batchRemove(c, false); } /** * 僅保留此列表中包含在指定集合中的元素。 *換句話說,從此列表中刪除其中不包含在指定集合中的所有元素。 */ public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true); } /** * 從列表中的指定位置開始,返回列表中的元素(按正確順序)的列表叠代器。 *指定的索引表示初始調用將返回的第一個元素為next 。 初始調用previous將返回指定索引減1的元素。 *返回的列表叠代器是fail-fast 。 */ public ListIterator<E> listIterator(int index) { if (index < 0 || index > size) throw new IndexOutOfBoundsException("Index: "+index); return new ListItr(index); } /** *返回列表中的列表叠代器(按適當的順序)。 *返回的列表叠代器是fail-fast 。 */ public ListIterator<E> listIterator() { return new ListItr(0); } /** *以正確的順序返回該列表中的元素的叠代器。 *返回的叠代器是fail-fast 。 */ public Iterator<E> iterator() { return new Itr(); }
ArrayList源碼分析
System.arraycopy()和Arrays.copyOf()方法
通過上面源碼我們發現這兩個實現數組復制的方法被廣泛使用而且很多地方都特別巧妙。比如下面add(int index, E element)方法就很巧妙的用到了arraycopy()方法讓數組自己復制自己實現讓index開始之後的所有成員後移一個位置:
/**
* 在此列表中的指定位置插入指定的元素。
*先調用 rangeCheckForAdd 對index進行界限檢查;然後調用 ensureCapacityInternal 方法保證capacity足夠大;
*再將從index開始之後的所有成員後移一個位置;將element插入index位置;最後size加1。
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//arraycopy()方法實現數組自己復制自己
//elementData:源數組;index:源數組中的起始位置;elementData:目標數組;index + 1:目標數組中的起始位置; size - index:要復制的數組元素的數量;
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
又如toArray()方法中用到了copyOf()方法
/**
*以正確的順序(從第一個到最後一個元素)返回一個包含此列表中所有元素的數組。
*返回的數組將是“安全的”,因為該列表不保留對它的引用。 (換句話說,這個方法必須分配一個新的數組)。
*因此,調用者可以自由地修改返回的數組。 此方法充當基於陣列和基於集合的API之間的橋梁。
*/
public Object[] toArray() {
//elementData:要復制的數組;size:要復制的長度
return Arrays.copyOf(elementData, size);
}
兩者聯系與區別
聯系:
看兩者源代碼可以發現copyOf()
內部調用了System.arraycopy()
方法
區別:
- arraycopy()需要目標數組,將原數組拷貝到你自己定義的數組裏,而且可以選擇拷貝的起點和長度以及放入新數組中的位置
copyOf()是系統自動在內部新建一個數組,並返回該數組。
ArrayList 核心擴容技術
//下面是ArrayList的擴容機制
//ArrayList的擴容機制提高了性能,如果每次只擴充一個,
//那麽頻繁的插入會導致頻繁的拷貝,降低性能,而ArrayList的擴容機制避免了這種情況。
/**
* 如有必要,增加此ArrayList實例的容量,以確保它至少能容納元素的數量
* @param minCapacity 所需的最小容量
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
//得到最小擴容量
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 獲取默認的容量和傳入參數的較大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//判斷是否需要擴容,上面兩個方法都要調用
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果說minCapacity也就是所需的最小容量大於保存ArrayList數據的數組的長度的話,就需要調用grow(minCapacity)方法擴容。
//這個minCapacity到底為多少呢?舉個例子在添加元素(add)方法中這個minCapacity的大小就為現在數組的長度加1
if (minCapacity - elementData.length > 0)
//調用grow方法進行擴容,調用此方法代表已經開始擴容了
grow(minCapacity);
}
/**
* ArrayList擴容的核心方法。
*/
private void grow(int minCapacity) {
//elementData為保存ArrayList數據的數組
///elementData.length求數組長度elementData.size是求數組中的元素個數
// oldCapacity為舊容量,newCapacity為新容量
int oldCapacity = elementData.length;
//將oldCapacity 右移一位,其效果相當於oldCapacity /2,
//我們知道位運算的速度遠遠快於整除運算,整句運算式的結果就是將新容量更新為舊容量的1.5倍,
int newCapacity = oldCapacity + (oldCapacity >> 1);
//然後檢查新容量是否大於最小需要容量,若還是小於最小需要容量,那麽就把最小需要容量當作數組的新容量,
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//再檢查新容量是否超出了ArrayList所定義的最大容量,
//若超出了,則調用hugeCapacity()來比較minCapacity和 MAX_ARRAY_SIZE,
//如果minCapacity大於最大容量,則新容量則為ArrayList定義的最大容量,否則,新容量大小則為 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);
}
擴容機制代碼已經做了詳細的解釋。另外值得註意的是大家很容易忽略的一個運算符:移位運算符
簡介:移位運算符就是在二進制的基礎上對數字進行平移。按照平移的方向和填充數字的規則分為三種:<<(左移)、>>(帶符號右移)和>>>(無符號右移)。
作用:對於大數據的2進制運算,位移運算符比那些普通運算符的運算要快很多,因為程序僅僅移動一下而已,不去計算,這樣提高了效率,節省了資源
比如這裏:int newCapacity = oldCapacity + (oldCapacity >> 1);
右移一位相當於除2,右移n位相當於除以 2 的 n 次方。這裏 oldCapacity 明顯右移了1位所以相當於oldCapacity /2。
另外需要註意的是:
java 中的length 屬性是針對數組說的,比如說你聲明了一個數組,想知道這個數組的長度則用到了 length 這個屬性.
java 中的length()方法是針對字 符串String說的,如果想看這個字符串的長度則用到 length()這個方法.
.java 中的size()方法是針對泛型集合說的,如果想看這個泛型有多少個元素,就調用此方法來查看!
內部類
(1)private class Itr implements Iterator<E>
(2)private class ListItr extends Itr implements ListIterator<E>
(3)private class SubList extends AbstractList<E> implements RandomAccess
(4)static final class ArrayListSpliterator<E> implements Spliterator<E>
ArrayList有四個內部類,其中的Itr是實現了Iterator接口,同時重寫了裏面的hasNext(),next(),remove()等方法;其中的ListItr繼承Itr,實現了ListIterator接口,同時重寫了hasPrevious(),nextIndex(),previousIndex(),previous(),set(E e),add(E e)等方法,所以這也可以看出了Iterator和ListIterator的區別:ListIterator在Iterator的基礎上增加了添加對象,修改對象,逆向遍歷等方法,這些是Iterator不能實現的。
ArrayList經典Demo
package list;
import java.util.ArrayList;
import java.util.Iterator;
public class ArrayListDemo {
public static void main(String[] srgs){
ArrayList<Integer> arrayList = new ArrayList<Integer>();
System.out.printf("Before add:arrayList.size() = %d\n",arrayList.size());
arrayList.add(1);
arrayList.add(3);
arrayList.add(5);
arrayList.add(7);
arrayList.add(9);
System.out.printf("After add:arrayList.size() = %d\n",arrayList.size());
System.out.println("Printing elements of arrayList");
// 三種遍歷方式打印元素
// 第一種:通過叠代器遍歷
System.out.print("通過叠代器遍歷:");
Iterator<Integer> it = arrayList.iterator();
while(it.hasNext()){
System.out.print(it.next() + " ");
}
System.out.println();
// 第二種:通過索引值遍歷
System.out.print("通過索引值遍歷:");
for(int i = 0; i < arrayList.size(); i++){
System.out.print(arrayList.get(i) + " ");
}
System.out.println();
// 第三種:for循環遍歷
System.out.print("for循環遍歷:");
for(Integer number : arrayList){
System.out.print(number + " ");
}
// toArray用法
// 第一種方式(最常用)
Integer[] integer = arrayList.toArray(new Integer[0]);
// 第二種方式(容易理解)
Integer[] integer1 = new Integer[arrayList.size()];
arrayList.toArray(integer1);
// 拋出異常,java不支持向下轉型
//Integer[] integer2 = new Integer[arrayList.size()];
//integer2 = arrayList.toArray();
System.out.println();
// 在指定位置添加元素
arrayList.add(2,2);
// 刪除指定位置上的元素
arrayList.remove(2);
// 刪除指定元素
arrayList.remove((Object)3);
// 判斷arrayList是否包含5
System.out.println("ArrayList contains 5 is: " + arrayList.contains(5));
// 清空ArrayList
arrayList.clear();
// 判斷ArrayList是否為空
System.out.println("ArrayList is empty: " + arrayList.isEmpty());
}
}
ArrayList 源碼分析