Java學習筆記 -- ArrayList集合分析
1.ArrayList簡介
ArrayList
底層是用陣列實現的,並且它是動態陣列,也就是它的容量是可以自動增長的。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
// 略...
}
-
實現
RandomAccess
介面:所以ArrayList
支援快速隨機訪問,本質上是通過下標序號隨機訪問。 -
實現
Serializable
介面:使ArrayList
支援序列化,通過序列化傳輸。 -
實現
Cloneable
介面:使ArrayList
能夠克隆。
1.1.底層關鍵
ArrayList
底層本質上是一個數組,用該陣列來儲存資料:
transient Object[] elementData;
transient
:Java關鍵字,變數修飾符,如果用transient宣告一個例項變數,當物件儲存時,它的值不需要維持。換句話來說就是,用transient關鍵字標記的成員變數不參與序列化過程。
1.2.追蹤原始碼
1.2.1.重要屬性
// 預設容量 private static final int DEFAULT_CAPACITY = 10; // 返回值Object型別的空陣列 private static final Object[] EMPTY_ELEMENTDATA = {}; // 預設容量空陣列 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 儲存我們新增的資料,關鍵 transient Object[] elementData; // 集合資料長度,因為沒有給預設值所以就是0 private int size;
1.2.2.無參構造
1、在main函式例項化集合物件,並新增元素
public static void main(String[] args) { List<Integer> list = new ArrayList<>(); // 1.首先新增10個元素 for (int i = 1; i <= 10; i++) { list.add(i); } // 2.再次新增5個元素 for (int i = 10; i <= 15; i++) { list.add(i); } list.add(100); list.add(200); }
2、在第一行設斷點,進行Debug,進入到該類的無參建構函式:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
可以看到,在建構函式裡給存放資料的陣列初始化容量大小為空,因為DEFAULTCAPACITY_EMPTY_ELEMENTDATA
就是空陣列。
所以這個時候 list = [ ]
4、第一次走到list.add(i)
進入到原始碼裡,如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
可以看到,add()
內部首先呼叫ensureCapacityInternal()
方法,將集合資料長度先加一,再傳入,也就是說第一次傳入的是值是1,因為原先預設為0。該方法主要確定陣列容量是否足夠,是否需要擴容,並不涉及元素的新增,非常重要。
5、進入到該方法當中:
// minCapacity最小容量,首次為 size + 1 = 1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
可以看到,又呼叫了方法的過載,並且首先呼叫calculateCapacity()
方法,並且將存放資料的陣列和新增資料到當前時刻的大小傳入,比如第一次新增資料,minCapatiry
的大小等於1,第二次就等於2,然後呼叫過載方法,將calculateCapacity()
計算的返回結果傳入。
6、首先進入到calculateCapacity()
:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
-
第一次進入到這裡邊,
elementData
是空的,minCapacity = 1
,而判斷語句就是判斷陣列是否為空,所以第一次進入到該方法時,這裡的判斷條件是成立的。 -
從預設容量
DEFAULT_CAPACITY
和當前資料大小minCapacity
當中取一個最大值返回。因為第一次進入,DEFAULT_CAPACITY = 10
,minCapacity
是傳入的size + 1 = 1
。
還要一點需要注意的是:如果這裡的
minCapacity
只在集合新增時使用,那直接返回DEFAULT_CAPACITY
即可,因為首次新增資料時minCapacity
為1,而DEFAULT_CAPACITY
為10,一定大於minCapacity
,而下次新增資料陣列就不為空所以往後該判斷均不成立,所以minCapacity
不可能會比DEFAULT_CAPACITY
大,之所以還要呼叫方法挑選最大的返回,是因為ArrayList
支援序列化和反序列化,如果直接通過反序列化獲得一個集合扔過來,那麼minCapacity
的值可能是非常大的,這個時候是需要基於當前大小進行再次擴容。
所以、第一次最終的返回結果就是 10,這個10代表我們需要的陣列容量大小,也就是說、首次新增資料發現數組為空,就直接擴容到10的大小。
6、進入過載方法:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
-
首先是
modCount++
,這是記錄集合被修改的次數,因為ArrayList
是非執行緒安全的。 -
接著判斷我們需要的最小容量減去當前資料長度是否大於0,如果條件成立,說明實際需要的容量大小已經大於原始陣列容量的大小了,所以就呼叫
grow()
進行擴容,第一次新增資料,calculateCapacity()
返回的結果是return Math.max(DEFAULT_CAPACITY, minCapacity)
,也就是10,說明我們實際需要的容量為10才夠用,但是陣列卻是空的,所以條件必定成立,然後進行擴容,如下:
7、進入到grow()
方法當中:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
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);
}
如下分析:
- 首先獲取陣列長度,賦值給
oldCapacity
,也就是0,這是原始容量大小。 - 然後進行位運算,向右移一位,也就是原始資料除以2,然後加上
oldCapacity
,由於是第一次,所以0 + 0 = 0,這是進行位運算後或者是擴容後的新容量大小。 - 這一步非常關鍵,判斷擴容後的容量減去最小容量是否小於0,如果條件成立,就將執行
newCapacity = minCapacity
。也就是執行了newCapacity = 10
,最後將新的容量複製給原始陣列,也就是將一個空陣列擴容到了大小為 10 的陣列。
所以得出結論:初始化ArrayList
陣列預設為空,新增第一個資料之前將陣列大小擴容到10,往後如果超出10,就會基於10進行1.5倍擴容,這裡設計的非常繞,繞來繞去的就是為了擴容到10。
1.2.3.有參構造
如果使用的是有參構造,則初始化容量為指定大小,如下:
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);
}
}
新增資料呼叫add()
方法,進入calculateCapacity()
方法,如下:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
由於例項化的是有參構造,在裡邊指定了陣列大小,這時判斷就不成立了,因為陣列不為空,而第一次新增資料minCapacity
依舊為1。
接著進入ensureExplicitCapacity()
:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
下方的條件依舊不成立,第一次新增資料minCapacity
等於1,而陣列大小可能為我們指定的8,所以不需要擴容。
所以:如果使用有參構造例項化集合物件,則初始化elementData
為指定大小,如果需要擴容,則直接擴容elementData
的1.5倍。