【Java容器】ArrayList擴容機制及原始碼分析
阿新 • • 發佈:2021-01-20
ArrayList原始碼分析及擴容機制
一、ArrayList介紹:
ArrayList 的底層是陣列佇列(Object[]),相當於動態陣列。與 Java 中的陣列相比,它的容量能動態增長。
ArrayList 不保證執行緒安全;
在新增大量元素前,應用程式可以使用ensureCapacity操作來增加 ArrayList 例項的容量。這可以減少遞增式再分配的數量。
建構函式:
/**
*預設無參建構函式
*/
public ArrayList() {
}
/**
* 帶初始容量引數的建構函式 */
public ArrayList(int initialCapacity) {
}
/**
* 構造一個包含指定集合的元素的列表,按照它們由集合的迭代器返回的順序。
*/
public ArrayList(Collection<? extends E> c) {
}
二、ArrayList原始碼分析:
2.1 AL的父親們:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- RandomAccess 是一個標誌介面(在某些排序和搜尋演算法中會檢查是否是繼承這個標誌介面),表明實現這個這個介面的 List 集合是支援快速隨機訪問的。
- ArrayList 實現了 Cloneable 介面 ,即覆蓋了函式clone(),能被克隆。
- ArrayList 實現了 java.io.Serializable 介面,這意味著ArrayList支援序列化,能通過序列化去傳輸。
2.2 AL的初始化,你不用就給你個空的:
// 初始大小
private static final int DEFAULT_CAPACITY = 10;
// 初始空陣列
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// ArrayList的底層實現陣列
transient Object[] elementData;
/**
無引數建構函式,可見如果我們沒有給定初始值,AL只是建立了一個空陣列“糊弄人”
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
通過上面我們看到了ArrayList無參初始化時並沒有給定陣列容量,其實這裡就牽扯出了ArrayList的擴容機制:
三、ArrayList擴容分析:
3.1 add()方法,你用我我才給你開闢空間:
add()方法:(這裡是我簡化合並後的核心程式碼)
// AL 的元素個數
private int size;
/**
* 將指定的元素追加到此列表的末尾。
*/
public boolean add(E e) {
int minCapacity = size + 1;
// 如果是預設初始化的陣列
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 獲取預設的容量和size + 1的較大值,+1是因為add發生時正在存入第一個元素
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 如果add會發生記憶體溢位的情況,第一次add時便會最終進入grow擴容
if (minCapacity - elementData.length > 0){
//呼叫grow方法進行擴容
grow(minCapacity);
}
// 這裡看到ArrayList新增元素的實質就相當於為陣列賦值
elementData[size++] = e;
return true;
}
分析一波:
其實add()方法最核心的判斷就在於if (minCapacity - elementData.length > 0),我再翻譯一下就是if (size + 1 - elementData.length > 0),也就是判斷這次add是否會超過這時AL的容量,如果超過,那麼就進入grow()方法進行擴容,第一次add()方法執行時,因為minCapacity=10 而elementData.length=0所以也會進grow()方法。
3.2 grow() :真正的擴容方法是我:
grow()方法:(同樣是簡化版本)
// 要分配的最大陣列大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// 舊容量
int oldCapacity = elementData.length;
// oldCapacity /2,新容量實際是 oldCapacity * 1.5
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 然後*1.5都不夠的話,就要多少給多少,直接等於minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量大於了AL定義的最大容量,那就交給minCapacity來確定一個合適容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
// 複製擴容操作
elementData = Arrays.copyOf(elementData, newCapacity);
}
至於最後的copyOf方法內部實際呼叫了 System.arraycopy() 方法來實現陣列的複製,通過複製來擴容。
這是總結:
無參初始容量零,一次add變成10,
1.5倍不夠爽,最小容量來確定,
雖然陣列有上限,動態陣列還是爽