ArrayList構造方法的源碼暗藏玄機?

ArrayList集合底層數據結構

ArrayList集合介紹
  • List 接口的可調整大小的數組實現。

  • 數組:一旦初始化長度就不可以發生改變 。

數組結構介紹
  • 增刪慢:每次刪除元素,都需要更改數組長度、拷貝以及移動元素位置。

  • 查詢快:由於數組在內存中是一塊連續空間,因此可以根據地址+索引的方式快速獲取對應位置上的元素。

源碼中定義量

默認的初始化容量
private static final int DEFAULT_CAPACITY = 10;
空數組沒有默認容量
private static final Object[] EMPTY_ELEMENTDATA = {};
默認容量的空數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
集合真正存儲數組元素的數組。ArrayList的底層數據結構,transient表示該字段不進行序列化操作
transient Object[] elementData;
ArrayList的大小,就是集合中元素的個數
private int size;

構造方法表格

Constructor Constructor描述
ArrayList() 構造一個初始容量爲十的空列表。
ArrayList(int initialCapacity) 構造具有指定初始容量的空列表。
ArrayList(Collection<? extends E> c) 構造一個包含指定集合的元素的列表,按照它們由集合的迭代器返回的順序。

空參構造ArrayList()

源碼解析
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
案例演示
@Test
public void test_get(){
	// 運行這行代碼真的會構造一個初始容量爲10的集合嗎?
	List<Integer> list = new ArrayList<>();
}
驗證是否構造一個初始容量爲10的集合
@Test
public void test_get(){
   try {
      List<String> list = new ArrayList<>();
      Field field = list.getClass().getDeclaredField("elementData");
      field.setAccessible(true);
      int size = ((Object[]) field.get(list)).length;
      System.out.println(size);// 0 事實證明並沒有初始化一個容量爲10的集合
      // 添加第一個元素
      list.add("1");
      Field field2 = list.getClass().getDeclaredField("elementData");
      field2.setAccessible(true);
      int size2 = ((Object[]) field2.get(list)).length;
      System.out.println(size2);// 10 事實證明添加第一個元素的時候才進行初始化容量10
   } catch (Exception e) {
      e.printStackTrace();
   }
}

結論:通過以上的代碼演示證明空參構造方法創建集合對象並未構造一個初始容量爲十的空列表,僅僅將DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的地址賦值給elementData。只有添加第一個元素的時候纔會將容量初始化爲10

指定容量ArrayList(int initialCapacity)

源碼解析
public ArrayList(int initialCapacity) {
    // 容量大於0,按照指定的容量初始化數組
    if (initialCapacity > 0) {
        // 創建一個數組,且指定長度爲initialCapacity
        this.elementData = new Object[initialCapacity];
    // 如果initialCapacity容量爲0,把EMPTY_ELEMENTDATA的地址賦值給elementData
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    // 容量小於0,拋非法異常    
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    }
}
代碼演示
@Test
public void test_c(){
	// 創建一個帶有初始容量的集合,這個集合創建以後真的初始容量爲5嗎?
	List<Integer> list = new ArrayList<>(5);
}
驗證是否構造一個我們指定初始容量5的集合
@Test
public void test_get(){
	try {
    // 構造一個長度爲5的集合
		List<String> list = new ArrayList<>(5);
		Field field = list.getClass().getDeclaredField("elementData");
		field.setAccessible(true);
		int size = ((Object[]) field.get(list)).length;
		System.out.println(size);// 5 確實是我們自己指定的容量
	} catch (Exception e) {
		e.printStackTrace();
	}
}

結論:根據 ArrayList 構造方法參數創建指定長度的數組。

按照集合迭代器返回的順序,構造一個list包含指定集合的元素。c參數:元素將被放到list中的集合指定的集合爲null,將會拋出NullPointerException

指定集合ArrayList(Collection<? extends E> c)

源碼解析
public ArrayList(Collection<? extends E> c) {
    // 將給定的集合對象轉成數組,且將數組的地址賦值給elementData
    elementData = c.toArray();
    // 將elementData的長度賦值給集合長度size,且判斷是否不等於 0
    if ((size = elementData.length) != 0) {
        // 判斷elementData 和 Object[] 是否爲不一樣的類型
        // c.toArray()數組不是object數組進行轉換成Object[]
        // 每個集合的toarray()的實現方法不一樣,所以需要判斷一下,如果不是Object[].class類型,那麼就需要使用ArrayList中的方法去改造一下。
        // elementData.getClass()到底是什麼類型的?下面搞個例子測試一下
        if (elementData.getClass() != Object[].class)
            // 轉換Object[] 【在我的其他文章中的新增源碼裏面有分析】
            // 如果不一樣,使用Arrays的copyOf方法進行元素的拷貝
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 給定的數組的長度爲0,用空數組代替
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
toArray()方法及其實現源碼
// Collection<E>接口中轉數組接口
// 返回一個包含此集合中所有元素的數組。這個方法可以保證給定集合的順序返回數組。此方法充當基於數組的API和基於集合的API之間的橋樑。
Object[] toArray();

// ArrayList<E>中的toArray()方法
public Object[] toArray() {
  // 調用數組工具類方法進行拷貝
	return Arrays.copyOf(elementData, size);
}
Arrays類中的copyOf方法源碼
// Arrays類中的copyOf方法進行數組的拷貝。original原始的數組,newLength新的容量
public static <T> T[] copyOf(T[] original, int newLength) {
  // 再次調用方法進行拷貝
	return (T[]) copyOf(original, newLength, original.getClass());
}

// 將原始的數組copy到新的容量的數組中的具體實現
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
	@SuppressWarnings("unchecked")
  // 用三元運算符進行判斷,不管結果如何都是創建一個新數組
	T[] copy = ((Object)newType == (Object)Object[].class)
			? (T[]) new Object[newLength]
			: (T[]) Array.newInstance(newType.getComponentType(), newLength);
	// 將數組的內容拷貝到 copy 該數組中,使用System.arraycopy 將需要插入的位置(index)後面的元素統統往後移動一位
  System.arraycopy(original, 0, copy, 0,
			Math.min(original.length, newLength));
  // 返回拷貝元素成功後的數組
	return copy;
}
arraycopy方法
/*
* @param      src      the source array.原始的數組
* @param      srcPos   starting position in the source array.在原始數組中開始的位置
* @param      dest     the destination array.目標數組
* @param      destPos  starting position in the destination data.在目標數組中的起始位置
* @param      length   the number of array elements to be copied.要copy的元素的個數
*/
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);
測試elementData.getClass()到底是什麼類型
@Test
public void test_c(){
	List<Integer> list = new ArrayList<>();
	list.add(1);
  // getClass()這個方法返回的是該對象的運行時類。
	Class<? extends Object[]> aClass = list.toArray().getClass();
  System.out.println(aClass);// class [Ljava.lang.Object;
	if (aClass != Object[].class){
		System.out.println(false); 
	}else {
		System.out.println(true);// 打印結果:true
	}
}
上面分析了半天的Arrays.copyOf方法接下來驗證一下
@Test
public void test_arrays_copy_of(){
	Object[] str = new Object[]{"1", "2"};
  // 1代表的是要拷貝元素的個數
	Object[] arr_ = Arrays.copyOf(str, 1);
	System.out.println(Arrays.toString(arr_));
	// [1]

	Object[] str0 = new Object[]{"3", "4"};
	Object[] arr0_ = Arrays.copyOf(str0, 2);
	System.out.println(Arrays.toString(arr0_));
	// [3, 4]

	String[] str1 = new String[]{"5", "6"};
	String[] arr1_ = Arrays.copyOf(str1, 5);
	System.out.println(Arrays.toString(arr1_));
	//[5, 6, null, null, null] 元素不夠會用null填充
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章