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

前言

點贊在看,養成習慣。web

點贊收藏,人生輝煌。面試

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

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個元素之後底層數組中的元素
  • 添加第三個元素查看到底是如何添加的?

結論

使用該方法的話將致使指定位置後面的數組元素所有從新移動,即日後移動一位。數組

一、確保數插入的位置小於等於當前數組長度,而且不小於0,不然拋出異常。微信

二、確保數組已使用長度(size)加1以後足夠存下 下一個數據。編輯器

三、修改次數(modCount)標識自增1,若是當前數組已使用長度(size)加1後的大於當前的數組長度,則調用grow方法,增加數組。工具

四、grow方法會將當前數組的長度變爲原來容量的1.5倍。。源碼分析

五、確保有足夠的容量以後,使用System.arraycopy 將須要插入的位置(index)後面的元素通通日後移動一位。post

六、將新的數據內容存放到數組的指定位置(index)上。url

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環境下。

ArrayList源碼系列

第一篇:ArrayList中的構造方法源碼在面試中被問到了...抱歉沒準備好!!!告辭
第二篇:面試官讓我講ArrayList中add、addAll方法的源碼...我下次再來
第三篇:工做兩年還沒看過ArrayList中remove、removeAll、clear方法源碼的都來報道吧

創做不易, 很是歡迎你們的點贊、評論和關注(^_−)☆

你的點贊、評論以及關注是對我最大的支持和鼓勵,而你的支持和鼓勵

我繼續創做高質量博客的動力 !!!

相關文章
相關標籤/搜索