Debug ArrayList源碼

1,ArrayList面試必問

  • 說說ArrayList和LinkedList的區別?

    ArrayList基於數組實現,LinkedList基於鏈表實現,不一樣的數據結構決定了ArrayList查詢效率比較高,而LinkedList插入刪除效率比較高,反過來就比較慢了。java

  • ArrayList默認初始容量爲多少?按照幾倍來擴容?

    10,1.5倍。面試

  • 說說數組擴容的原理?

    ArrayList擴容調用的是Array.copyof函數,把老數組遍歷賦值給新數組返回。數組

  • 說說ArrayList常見方法的時間複雜度?安全

    • get方法經過下標獲取元素,時間複雜度爲O(1)
    • add方法直接添加會添加到集合的尾部,時間複雜度爲O(1)
    • add方法經過下標添加到非尾部會引發數組的批量移動,時間複雜度爲O(n),不然爲O(1)
    • remove方法經過下標刪除非尾部元素引發數組批量移動,時間複雜度爲O(n),反之則爲O(1)
    • remove方法根據對象刪除須要遍歷集合,時間複雜度爲O(n),若是刪除的爲非尾部元素,會使數組批量移動,時間複雜度爲O(n^2)
總之,經過下標操做的時間複雜度爲O(1),若是觸發了數組的批量移動,時間複雜度爲O(n),若是經過對象操做須要遍歷集合,時間複雜度已經爲O(n),若同時觸發了數組的移動,時間複雜度爲O(n^2).
  • ArrayList和vector的區別數據結構

    • 最大的區別在於線程是否安全
    • 其次Vector是兩倍擴容
    • 最後就是在不指定大小的狀況下,ArrayList容量初始化是在添加元素的時候,而Vector有一個無參構造器直接初始化爲10

2,Debug ArrayList源碼

因爲1.7和1.8幾乎沒什麼變化,本文以jdk1.8爲例。函數

2.1 用Debug分析一個元素是如何add進ArrayList

編寫測試用例,打上斷點:測試

image-20200718134052340

先分析構造函數如何初始化,關鍵步驟以下:spa

image-20200718134136152

elementData是ArraList底層數組的實現,(ps:hashMap數組使用table命名)線程

image-20200718134210282

DEFAULTCAPACITY_EMPTY_ELEMENTDATA表示默認的空數組,也就是說ArrayList在構造函數初始化時並不會進行底層數組的初始化。debug

image-20200718134341634

給元素的添加打上斷點,分析過程:

image-20200718134636053

進入add方法內部:

public boolean add(E e) {
//確保內部容量,在元素添加進來前可能要進行擴容操做,size初始化爲0,表示集合的長度
ensureCapacityInternal(size + 1); // Increments modCount!!
//添加元素,size自增
elementData[size++] = e;
return true;
}

進入ensureCapacityInternal方法內部:此時elementData爲空,size+1=minCapacity=1

ensureExplicitCapacity:確保明確的能力

image-20200718135014136

計算容量,calculateCapacity方法:

private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判斷數組是否爲空,若爲空,返回默認容量和最小容量的最大值,若不爲空,返回最小容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}

DEFAULT_CAPACITY默認容量爲10:

image-20200718135315367

繼續分析,進入:

ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));此時參數爲10,也就是ArrayList的默認容量

private void ensureExplicitCapacity(int minCapacity) {
modCount++;   //集合的修改次數

//若是最小容量減去數組長度大於0,進行擴容,此時最小容量爲10,數組長度爲0
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

核心擴容函數grow:(ps:HashMap中擴容函數爲resize)

private void grow(int minCapacity) {
//oldCapacity:舊數組容量
int oldCapacity = elementData.length;

//新容量等於舊容量加上舊容量的一半,>>1至關於除以2(ArrayList擴容是1.5倍)
int newCapacity = oldCapacity + (oldCapacity >> 1);

//新容量小於最小容量,則賦值爲最小容量,此時newCapacity等於0,minCapacity爲10
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;

//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//賦值給新數組
elementData = Arrays.copyOf(elementData, newCapacity);
}

數組複製Arrays.copyOf:

image-20200718142335257

image-20200718142450568

2.2 用Debug分析如何經過數組下標獲取ArrayList元素

打上斷點,debug:

image-20200718143338620

首先進行範圍檢查,然後返回元素

image-20200718143422580

image-20200718143458143

2.3 用Debug分析如何經過數組下標刪除一個元素

打上斷點:

image-20200718143953768

進入remove方法內部,

public E remove(int index) {
//下標範圍檢查
rangeCheck(index);
//修改次數自增
modCount++;
//保留當前刪除元素的值,稍後返回
E oldValue = elementData(index);
//須要移動元素的個數
int numMoved = size - index - 1;
if (numMoved > 0)
//底層使用native方法,debug進不去。native方法:java調用其餘語言的接口
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//最後一位置空
elementData[--size] = null; // clear to let GC do its work
//返回刪除元素的值
return oldValue;
}

2.4 用Debug分析如何經過對象刪除一個元素

image-20200718144826001

進入remove方法:

public boolean remove(Object o) {
//若是對象爲空,則遍歷ArrayList集合
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;
}

進入fastRemove方法:

private void fastRemove(int index) {
modCount++;

//numMoved:須要移動數組的個數
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 wrk
}

2.5 用Debug分析向數組中間添加元素

image-20200718150021832

進入add方法

public void add(int index, E element) {
//下標範圍檢查
rangeCheckForAdd(index);
//確保容量大小
ensureCapacityInternal(size + 1); // Increments modCount!!

//移動數組位置
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//賦值給數組
elementData[index] = element;
//元素個數增長
size++;
}

關於System.arraycopy時間複雜度問題,在添加或者刪除最後一個元素的時候不會觸發數組的複製機制,時間複雜度爲O(1),如果添加到數組中間,因爲會觸發數組的複製,時間複雜度爲O(n)。對於刪除元素一樣,根據數組下標刪除的狀況下,刪除尾部元素是不會觸發數組的擴容機制的,若刪除中間的元素,一樣會觸發數組的複製。若根據對象刪除元素,因爲自己遍歷到對象的時間複雜度爲O(n),刪除元素後再對數組進行重組,因此時間複雜度爲O(n^2)。

相關文章
相關標籤/搜索