概述:ArrayList是一個可以動態增長的索引序列,底層是數組。
源碼分析:
繼承:
AbstractList抽象類
該類實現了List<E>接口一些通用方法,其子類可以得到這些通用方法,實現自己特有的方法,使得代碼更簡潔,減少重複代碼。
實現:
List接口,爲了設計。
RandomAccess接口,一個標記性接口,用來快速隨機存取,提高普通for循環遍歷的性能。
Cloneable接口,一個克隆接口,實現它就可以使用object.clone()方法。
Serializable接口,一個序列化接口,可以將類轉化成字節流傳輸。
特點:
1. 可以存放null值。
2. 底層是一個elementData數組。
3. grow()方法,ArrayList的核心方法,可以通過該方法進行擴容。
4. 查詢快,但是增刪慢。
屬性:
// 版本號
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;
// 實際元素大小,默認爲0
private int size;
// 最大數組容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
構造方法:
作用:初始化一個默認10的容量(也可以自定義容量)的 elementData 數組。
重點:在構造方法中,只是定義容量大小,還沒真正實現!(在grow方法實現)
1. 無參構造:
/**
* 默認10的容量
*/
public ArrayList() { //elementData其實就是底層數組
//這裏賦給一個空數組,容量在程序調用add()方法時,進行默認10的容量初始化。
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2. 有參構造:
/**
* 指定容量的初始化構造函數
* initialCapacity:指定數組容量大小
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity]; //設置自定義初始化容量的數組
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 將指定集合添加到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);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
add方法:
1.boolean add(E e) //添加元素,默認在末尾添加
2.void add(int index, E element) //在指定位置添加元素
3.boolean addAll(Collection<? extends E> c) //添加一個集合元素進本集合
4.boolean addAll(int index, Collection<? extends E> c)//添加一個集合元素進本集合的特定位置
1. add(E e)
/**
* 在數組的末尾添加元素
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); //判斷加入元素後的總數量有沒有超過數組容量
elementData[size++] = e; //添加元素e進數組,然後 size+1
return true;
}
下面是具體方法實現:
/**
* 先判斷底層數組是否爲空數組,如果爲空:minCapacity=10。如果不爲空:minCapacity原樣輸出
* minCapacity:數組的容量
* elementData:數組
*/
private void ensureCapacityInternal(int minCapacity) {
// 如果 elementData 是空數組,無參構造
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 賦值,默認爲容量爲10,除非minCapacity更大。
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
/**
* 判斷加入元素後的總數量有沒有超過數組容量
*
* minCapacity:數組的容量
* elementData:數組
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //修改次數增加
//一、elementDate 爲空數組,minCapacity爲10,10-0=10>0,進行第一次擴容,也即是數組容量初始化。
//二、elementDate 不爲空數組,判斷此時的元素總數是否大於數組的容量。
if (minCapacity - elementData.length > 0)
grow(minCapacity); //擴容
}
真正擴容和初始化數組容量的地方grow()方法。
/*
* 實現初始化容量和擴容
* */
private void grow(int minCapacity) {
// oldCapacity 數組大小,如果是無參構造的空數組,大小爲0,minCapacity爲10
int oldCapacity = elementData.length;
//擴容爲原來oldCapacity 的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0) //1.5倍不夠,或者elementData爲空數組(0-10=-10)
newCapacity = minCapacity; //要多少是什麼(空數組:初始化容量爲10)
if (newCapacity - MAX_ARRAY_SIZE > 0) //容量是否超過原本限制的最大容量MAX_ARRAY_SIZE
newCapacity = hugeCapacity(minCapacity); // 最多能給多少是多少
// 方法參數:elementData:要賦值的數組,newCapacity:複製後的容量大小
elementData = Arrays.copyOf(elementData, newCapacity); //搬遷數組。
}
/*
* 當新擴容的容量(1.5倍)大於 MAX_ARRAY_SIZE時,新容量的取值。
* 返回能夠取的最大值
* */
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 將未擴容的容量與 MAX_ARRAY_SIZE 比較
// MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
2.add(int index, E element)
/*
* 在指定索引位置添加元素
* index:指定索引
* element:要添加的元素
* */
public void add(int index, E element) {
rangeCheckForAdd(index); //檢查索引是否越界。
ensureCapacityInternal(size + 1); //判斷加入元素後的總數量有沒有超過數組容量。
System.arraycopy(elementData, index, elementData, index + 1, //將elementData在插入位置後的所有元素往後面移一位。
size - index); // index爲要移動前的起始位, index+1爲移動後的起始位,size-index爲移動數量。
elementData[index] = element; //添加元素。
size++;
}
/*
* 判斷輸入的 index 有沒有越界
* */
private void rangeCheckForAdd(int index) {
if (index > size || index < 0) //判斷index有沒有越界。
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
remove方法:
1. E remove(int index):刪除並返回指定索引的值
2. boolean remove(Object o):刪除該值
3. void clear():清理所有數據
4. boolean removeAll(Collection<?> c) :批量刪除
1. E remove(int index);
/*
* 在指定位置移除元素
* */
public E remove(int index) {
rangeCheck(index); //判斷是否越界
modCount++; //修改次數增加
E oldValue = elementData(index); //取出該值
// 刪除一個索引的元素,後面的元素都要往前移一位。
int numMoved = size - index - 1; //numMoved爲移動的元素數量
if (numMoved > 0) //(size-1) - index,判斷 index在[0,size-1)內,如果不是最後一個元素,不用移動數組。
//元素移動
//elementData:選擇數組。
//index+1:選擇數組中要複製元素的起始位。
//elementData:目標數組。
//index:目標數組要存放加進來元素的起始位。
//numMoved:移動的元素數量。
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; //清理原來 index=size-1的元素,size-1
return oldValue;
}
2. boolean remove(Object o);
/*
* 移除指定元素
* o:要刪除的元素
* */
public boolean remove(Object o) {
// 如果要刪除空值:使用 == 比較內容,否則使用equals
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])) { //上面排除的null值,可以使用equals方法,防止空指針異常。
fastRemove(index);
return true;
}
}
return false;
}
3. void clear():
public void clear() { //所有值賦null,方便回收
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
4. boolean removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false); // 調用batchRemove ,批量刪除c與elementData共同的元素
}
public boolean retainAll(Collection<?> c) { //也調用 batchRemove,求交集
Objects.requireNonNull(c);
return batchRemove(c, true);
}
//batchRemove
private boolean batchRemove(Collection<?> c, boolean complement) { //complement : true:求交集 flase:刪除
final Object[] elementData = this.elementData;
int r = 0, w = 0; //r控制循環,w爲交集個數
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement) //如果有交集,集合從index=0開始覆蓋元素,添加與集合c有交集的元素
elementData[w++] = elementData[r]; //如果是批量刪除,集合從index=0開始覆蓋元素,添加與集合c無交集的元素
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) { //contains異常處理
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
set方法
public E set(int index, E element) { //在指定位置置換元素
rangeCheck(index); //判斷 index 是否越界
E oldValue = elementData(index);
elementData[index] = element; //替換元素
return oldValue;
}
get方法
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
總結:
一、初始化容量數組步驟:
1. 構造方法定義容量大小,默認爲10。
2. add方法,確認容量的大小。
3. grow方法,真正的實現容量。
二、擴容
grow方法,正常情況下擴容爲原來的1.5倍。
三、遍歷
由於ArrayList底層是數組,實現了 RandomAccess 接口的list,支持快速隨機訪問,優先選擇普通 for 循環,其次 foreach。