面試官讓我講ArrayList中add、addAll方法的源碼...我下次再來

前言

點贊在看,養成習慣。

點贊收藏,人生輝煌。

點擊關注【微信搜索公衆號:編程背鍋俠】,防止迷路。

ArrayList中的添加方法總結

方法名 描述
public boolean add(E e) 將指定的元素追加到此列表的末尾。
public void add(int index, E element) 在此列表中的指定位置插入指定的元素。
public boolean addAll(Collection<? extends E> c) 按指定集合的Iterator返回的順序將指定集合中的所有元素 追加到此列表的末尾。
public boolean addAll(i nt index, Collection<? extends E> c) 將指定集合中的所有元素插入到此列表中,從指定的位置 開始。

public boolean add(E e) 添加單個元素

案例演示
@Test
public void test_add(){
	ArrayList<String> list = new ArrayList<>();
	// 這個add是怎麼執行的?帶着這個疑問看一下add的源碼,擼ta
	list.add("洛洛");
}
源碼分析
// e 要添加到此列表的元素
public boolean add(E e) {
  // 調用方法對內部容量進行校驗
  // 加入元素前檢查容量是否夠用,size是數組中數據的個數,因爲要添加一個元素,所以size+1,先判斷size+1的這個數組能否放得下,就在這個方法中去判斷是否【數組.length】是否夠用了。
	ensureCapacityInternal(size + 1);  // Increments modCount!!
  // 將指定的元素添加到數組的尾部
	elementData[size++] = e;
	return true;
}

// 計算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
	// 判斷集合存數據的數組是否等於空容量的數組
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    // 通過最小容量和默認容量 求出較大值 (用於第一次擴容),首次添加元素會進入到這個方法
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
  // 不爲空數組的容量
	return minCapacity;
}

private void ensureCapacityInternal(int minCapacity) {
  // 將calculateCapacity方法中計算出來的容量傳遞給ensureExplicitCapacity方法,繼續校驗
	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {
  // 實際修改集合次數++ (在擴容的過程中沒用,主要是用於迭代器中)
	modCount++;

	// 判斷最小容量 - 數組長度是否大於 0
	if (minCapacity - elementData.length > 0)
    // 將第一次計算出來的容量傳遞給核心擴容方法,參考下面的grow方法源碼分析
		grow(minCapacity);
}
底層數組elementData中元素變化圖解
  • 添加2個元素以後底層數組中的元素
    在這裏插入圖片描述
  • 添加第三個元素查看究竟是如何添加的?
    在這裏插入圖片描述
結論

通過源碼和以上兩張圖分析得到add方法,是在數組的尾部添加元素的。

public void add(int index, E element) 在指定索引處添加元素

案例演示
@Test
public void test_add_c(){
	ArrayList<String> list = new ArrayList<>();
	list.add("洛洛00");
	list.add("洛洛01");
	list.add(1, "洛洛05");
  // 執行上面的指定索引的插入方法以後這個打印結果將會如何呢?
	list.forEach(System.out::println);
}
源碼分析
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;
  // 長度加1
	size++;
}

// 檢查這個index是否在給定的集合的長度的範圍內
private void rangeCheckForAdd(int index) {
  // 給定的index大於集合的長度或者小於0,拋出空指針異常
	if (index > size || index < 0)
		throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

// 計算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
	// 判斷集合存數據的數組是否等於空容量的數組
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    // 通過最小容量和默認容量 求出較大值 (用於第一次擴容)
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
  // 返回最小容量
	return minCapacity;
}

// 保證容量夠用
private void ensureCapacityInternal(int minCapacity) {
	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

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

	// 如果再調用 add(index,element) 方法之前已經擴容,那麼源碼跟蹤到此結束不會進行擴容。
	if (minCapacity - elementData.length > 0)
		grow(minCapacity);
}

底層數組elementData中元素變化圖解
  • 指定位置添加元素前數組拷貝的準備
    在這裏插入圖片描述
  • 指定位置添加元素數組拷貝後的數組
    在這裏插入圖片描述
結論

將指定的元素插入此列表中的指定位置,並將當前處於該位置的元素(如果有的話)和隨後的任何元素向右移動(在其索引中增加一個)。

public boolean addAll(Collection<? extends E> c)` 將集合的所有元素一次性添加到集合

案例演示
@Test
public void test_addAll(){
	ArrayList<String> list = new ArrayList<>();
	// 這個add是怎麼執行的?
	list.add("洛洛00");
	list.add("洛洛01");
	list.add("洛洛02");
	list.add("洛洛03");
	list.add("洛洛04");

	ArrayList<String> all = new ArrayList<>();
	all.add("洛洛06");
	all.addAll(list);
	all.forEach(System.out::println);
}
源碼分析
public boolean addAll(Collection<? extends E> c) {
  // 把集合的元素轉存到Object類型的數組中
	Object[] a = c.toArray();
  // 記錄數組的長度
	int numNew = a.length;
  // 調用方法檢驗是否要擴容,且讓增量++
	ensureCapacityInternal(size + numNew);  // Increments modCount
  // 調用方法將a數組的元素拷貝到elementData數組中
	System.arraycopy(a, 0, elementData, size, numNew);
  // 新集合的長度爲a數組的長度加上集合的長度
	size += numNew;
  // 只要a數組【numNew】的長度不等於0,即說明添加成功
	return numNew != 0;
}

// 計算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
	// 判斷集合存數據的數組是否等於空容量的數組
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    // 通過最小容量和默認容量 求出較大值 (用於第一次擴容)
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
  // 返回最小容量
	return minCapacity;
}

// 保證容量夠用
private void ensureCapacityInternal(int minCapacity) {
	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

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

	// 如果再調用 add(index,element) 方法之前已經擴容,那麼源碼跟蹤到此結束不會進行擴容。
	if (minCapacity - elementData.length > 0)
		grow(minCapacity);
}
底層數組elementData中元素變化圖解
  • 添加前數組中的元素
    在這裏插入圖片描述
  • 添加後的數組中的元素
    在這裏插入圖片描述
結論

該方法是將指定的集合添加到集合的尾部。

public boolean addAll(int index, Collection<? extends E> c) 在指定的索引位置添加集合

案例演示
@Test
public void test_addAll_c(){
	ArrayList<String> list = new ArrayList<>(2);
	// 這個add是怎麼執行的?
	list.add("洛洛00");
	list.add("洛洛01");
	list.add("洛洛02");
	list.add("洛洛03");
	list.add("洛洛04");
	list.add("洛洛05");

	ArrayList<String> all = new ArrayList<>();
	all.add("洛洛06");
	all.add("洛洛07");
	all.add("洛洛08");
	all.addAll(1, list);
	all.forEach(System.out::println);
}
源碼分析
// index:要添加的指定位置,c:要添加的指定集合
public boolean addAll(int index, Collection<? extends E> c) {
  // 校驗索引
	rangeCheckForAdd(index);

  // 將給定的集合轉換成數組
	Object[] a = c.toArray();
  // 指定集合轉爲數組後的長度
	int numNew = a.length;
  // 確定當前的容量是否能夠滿足【原集合的size+指定集合的長度】目的就是爲了給集合存儲數據的數組進行擴容
	ensureCapacityInternal(size + numNew);  // Increments modCount
  
  // 計算要移動元素的個數,也就是插入位置及以後元素的個數
	int numMoved = size - index;
  // 移動元素的個數大於0,就將要移動的元素複製到指定位置
	if (numMoved > 0)
    // 複製要移動的元素到指定的位置,這一步的操作在原始數組中,給要添加的數組也留下了位置
		System.arraycopy(elementData, index, elementData, index + numNew,
				numMoved);
  // 複製指定的集合轉爲的數組到原始數組中
	System.arraycopy(a, 0, elementData, index, numNew);
  // 添加後的集合的長度
	size += numNew;
	return numNew != 0;
}

// 檢查這個index是否在給定的集合的長度的範圍內
private void rangeCheckForAdd(int index) {
  // 給定的index大於集合的長度或者小於0,拋出空指針異常
	if (index > size || index < 0)
		throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

// 計算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
	// 判斷集合存數據的數組是否等於空容量的數組
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    // 通過最小容量和默認容量 求出較大值 (用於第一次擴容)
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
  // 返回最小容量
	return minCapacity;
}

// 保證容量夠用
private void ensureCapacityInternal(int minCapacity) {
	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

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

	// 如果再調用 add(index,element) 方法之前已經擴容,那麼源碼跟蹤到此結束不會進行擴容。
	if (minCapacity - elementData.length > 0)
		grow(minCapacity);
}
底層數組elementData中元素變化圖解

指定位置添加數組前的準備
在這裏插入圖片描述
指定位置添加數組後新的數組
在這裏插入圖片描述

結論

該方法是在指定位置添加一個集合。通過上面的兩張圖裏面的elementData數組中的元素變化我們可以很清晰的瞭解是怎樣將元素在數組中添加集合的。

grow擴容方法

// 擴容方法增加容量以確保其至少可以容納最小容量參數指定的元素數。 minCapacity:最小的容量
private void grow(int minCapacity) {
	// 記錄數組的實際長度,此時由於沒有存儲元素,長度爲0
	int oldCapacity = elementData.length;
  // oldCapacity >> 1右移一位,新的容量爲舊的容量的1.5倍
	int newCapacity = oldCapacity + (oldCapacity >> 1);
  // 判斷新容量 - 最小容量是否小於0, 如果是第一次調用add方法必然小於0
	if (newCapacity - minCapacity < 0)
    // 將最小容量賦值給新容量,就是在這裏首次添加的時候將容量搞爲10的
		newCapacity = minCapacity;
  // 判斷新容量-最大數組大小是否>0,如果條件滿足就計算出一個超大容量
	if (newCapacity - MAX_ARRAY_SIZE > 0)
    // 計算出一個超大容量,並賦值給新的容量
		newCapacity = hugeCapacity(minCapacity);
	// 調用數組工具類方法,創建一個新數組,將新數組的地址賦值給elementData參看下方的Arrays.copyOf方法源碼分析
	elementData = Arrays.copyOf(elementData, newCapacity);
}
hugeCapacity方法
private static int hugeCapacity(int minCapacity) 
  // 最小容量小於0拋OutOfMemoryError異常
	if (minCapacity < 0) // overflow
		throw new OutOfMemoryError();
// 返回超大容量  
	return (minCapacity > MAX_ARRAY_SIZE) ?
			Integer.MAX_VALUE :
			MAX_ARRAY_SIZE;
}

Arrays.copyOf方法

ArrayList.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);
針對上面的方法案例分析
  • 不指定位置

arraycopy執行後添加到尾部

  • 指定位置

arraycopy執行後拷貝的是插入位置後的元素,拷貝到目標數組,操作的還是一個數組

JDK`版本

源碼以及案例演示都是在jdk1.8環境下。

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