ArrayList源碼學習筆記(1)

背景

之前關注了一個公衆號“彤哥讀源碼”,跟着一起學習了jdk裏的很多類的源碼,包括集合類、線程類、併發類。真的是很好的公衆號,哈哈(因爲確實是很好,這裏願意給路過的小夥伴安利一下,純自己主觀推薦,如果對公衆號作者有幫助,算是感謝這麼好的文章對我的幫助了~)

其中,ArrayList是最簡單的一個了,但是讀完別人寫的東西其實還是不如自己實踐來的實在。所以自己再搞點東西。

文章鏈接(侵刪):https://mp.weixin.qq.com/s/a0zq-q8JuSwsLYX7tJ4VxA

 

計劃

準備按照一個路徑走,邊看文章,邊看源碼,然後自己手動做些調試。順便寫點文章,記錄一下學習和調試過程。

之後,自己手動實現一個ArrayList,可以根據情況進行多版本迭代。

 

正文

成員變量很簡單,文章中也有介紹。ArrayList的元素存儲在Object[] elementData數組中,size是當前保存了多少個元素。

還有兩個靜態final的空數組EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA,看到這個就會產生疑惑,爲什麼會需要兩個靜態空數組呢?都是final的,反正不可變,用一個不就可以了?這其實跟構造函數有關係。

構造函數

ArrayList有三個構造方法,ArrayList()、ArrayList(int initialCapacity)、ArrayList(Collection c)。

ArrayList()會默認使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA作爲elementData的初始值;

ArrayList(int initialCapacity),如果initialCapacity > 0 就直接用這個值初始化數組長度了,等於0就直接用EMPTY_ELEMENTDATA。

ArrayList(Collection c) 如果c裏沒有元素,默認也是用EMPTY_ELEMENTDATA。

(上面說的很囉嗦,看代碼一目瞭然)

增長邏輯

此時重點來了,如果用ArrayList()和ArrayList(0)實例化了兩個對象,它們在增加第一個變量的時候,elementData會怎麼進行擴容?

這裏的判斷告訴我們它是怎麼做的。如果不手動指定初始容量,在第一次就會把容量設置爲10。如果手動指定的初始容量是0,那麼第一次就會把容量設置爲你需要的那個值(調用add方法的時候,其實就是1)。

一開始我產生了額外的疑問:爲什麼代碼寫的這麼囉嗦?minCapacity不就是1嗎,還能大於10?後來看了一下,確實能大於10,看一下addAll(Collection c)這個方法就明白了。如果一下子加入很多元素,也是會調用這個方法的。

那麼這個邏輯我們怎麼驗證一下呢?elementData是default的,自己寫代碼也沒權限獲取。這時候就是祭出“反射”大法的時候了,畢竟java裏“萬物皆可反射”😏

import java.lang.reflect.Field;
import java.util.ArrayList;

public class ArrayListTest {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        // 通過使用ArrayList()和ArrayList(0)來進行對比
        ArrayList<Integer> list = new ArrayList<>(0);
        Field field = list.getClass().getDeclaredField("elementData");
        field.setAccessible(true);
        Object []ele = (Object[]) field.get(list);
        System.out.println(ele.length);

        Field field1 = list.getClass().getDeclaredField("DEFAULTCAPACITY_EMPTY_ELEMENTDATA");
        field1.setAccessible(true);
        Object []ele1 = (Object[]) field1.get(null);
        // 這裏能發現ele和ele1是同一個對象
        System.out.println(ele);
        System.out.println(ele1);
        System.out.println(ele1 == ele);

        // 逐漸增加元素,會發現容量每次增長一半
        list.add(1);
        list.add(1);
        list.add(1);
        list.add(1);
        list.add(1);
        
        System.out.println(list.size());
        // 這裏不能打印ele.length,想想爲啥~(一開始就打錯了。。。)
        // 即便一開始的容量是大於0的,比如20,當元素增長超過20的時候,也不能用ele了,因爲複製產生了新數組
        System.out.println(((Object[]) field.get(list)).length);
    }
}

總結:

(1)如果不指定容量,新加入元素的時候容量最小是10;如果指定了容量爲0,新加入元素的時候,容量是加入元素的個數;

(2)之後的容量增長都是每次增長一半。10->15->22->33  或者 0->1->2->3->4->6->9

(3)萬物皆可反射~

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章