ArrayList原始碼解析(二)
歡迎轉載,轉載煩請註明出處,謝謝。
https://www.cnblogs.com/sx-wuyj/p/11177257.html
自己學習ArrayList原始碼的一些心得記錄.
繼續上一篇,ArrayList原始碼解析(一)
addll(Collection<? extends E> c) :新增目標集合到原有集合中.
//引數需要是Collection的子類 public boolean addAll(Collection<? extends E> c) { //將C集合通過toArray()方法轉為陣列 Object[] a = c.toArray(); //拿到傳入陣列的長度 int numNew = a.length; //對底層陣列進行擴容 ensureCapacityInternal(size + numNew); // Increments modCount //複製陣列 System.arraycopy(a, 0, elementData, size, numNew); //將size更新為size+numNew size += numNew; //返回boolean return numNew != 0; }
ensureCapacityInternal(size + numNew);
關於這個方法,在上一篇部落格當中有詳細解釋,可以去看看.
add(int index, Collection<? extends E> c):新增目標集合到原有集合指定位置
public boolean addAll(int index, Collection<? extends E> c) { //檢驗index rangeCheckForAdd(index); //將C集合通過toArray()方法轉為陣列 Object[] a = c.toArray(); //拿到傳入陣列的長度 int numNew = a.length; //對底層陣列進行擴容 ensureCapacityInternal(size + numNew); //計算需要複製的長度 int numMoved = size - index; //需要複製的長度如果大於0,通過arraycopy方法複製 if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); //將引數陣列複製到底層陣列index位置. System.arraycopy(a, 0, elementData, index, numNew); //修改size的值 為size+numNew size += numNew; //返回boolean return numNew != 0; }
其實addAll()方法只要理解擴容機制,其他的內容很簡單.但是這個方法需要注意的就是根據index去插入的時候.
if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew,numMoved); System.arraycopy(a, 0, elementData, index, numNew);
看見原始碼,可能會想為什麼要複製兩次呢?第一個判斷的時候,其實還有另一層含義:判斷插入位置是否在底層陣列中間,如果是則需要將插入的位置之後的元素向後移動,也就是複製一個新的陣列
第二次的複製才是將兩個數組合為一個數組,也就是底層陣列.
get(int index):獲取指定位置的元素
public E get(int index) { rangeCheck(index); return elementData(index); }
get方法大家都不陌生,原始碼也很簡單,或許對rangeCheck()方法比較陌生.
private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
僅此而已,我們傳入的index最大值不能超過size,這個很好理解.
set(int index, E element):修改指定位置的元素
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
set方法的原理其實就是將底層陣列indxe的元素修改傳入的元素,至於傳入的引數為什麼用泛型也很好理解,我們可以想一下我們工作學習中集合中元素的型別,各種各樣,所以在這個地方使用泛型的好處不言而喻.
還有一點就是set方法是有返回值的,我之前是沒有注意到的,返回值就是被修改的那個元素.
size():集合的大小
public int size() { return size; }
indexOf(object o):集合中某個元素第一次出現的位置
//集合中某個元素第一次出現的位置 public int indexOf(Object o) { //判斷傳入o是否為null if (o == null) { //如果為null,迴圈遍歷尋找為null的索引位置 for (int i = 0; i < size; i++) //因為傳入的是一個物件,為null使用== if (elementData[i]==null) //返回出現的位置 return i; } else { //如果不為null,使用equals尋找傳輸物件位置 for (int i = 0; i < size; i++) if (o.equals(elementData[i])) //找到返回物件位置 return i; } //找不到物件情況下返回-1 return -1; }
這個方法需要注意就是,如果傳入null的時候是需要用==去尋找的.
lastIndexOf(Object o):返回元素最後一次出現的位置
public int lastIndexOf(Object o) { //判斷傳入o是否為null if (o == null) { //如果為null,迴圈遍歷尋找為null的索引位置 //這裡是倒序遍歷 for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { //如果不為null,使用equals尋找傳輸物件位置 //同樣是倒序遍歷 for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; }
其實indeOf()和lastIndexOf()兩個方法的思路是一樣的,區別就在於一個為正序遍歷,一個倒序遍歷.
clone():克隆
public Object clone() { try { //呼叫了父類也就是Object的clone. ArrayList<?> v = (ArrayList<?>) super.clone(); //集合V的底層陣列複製一個原集合底層陣列 v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; //返回V集合 return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } }
這個方法我個人認為是不是返回值可以為ArrayList,從原始碼可以看出來返回去v是ArrayList.但是ArrayList的頂級父類也是Object.所以這個也不算錯.
try-catch中的註釋意思是 這是不可能發生的,因為我們是克隆的
toArray():將集合裝換為Object型別陣列
public Object[] toArray() { return Arrays.copyOf(elementData, size); }
其實就是將底層陣列複製一個返回去,底層呼叫的依然還是呼叫了System.arraycopy()這個方法,這裡不展開說.
toArray(T[] a):將集合裝換為指定型別陣列
public <T> T[] toArray(T[] a) { //如果引數陣列的長度小於size,那麼直接轉換為引數陣列型別 if (a.length < size) // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); //如果引數陣列長度大於size //那麼返回去的陣列[size]為null if (a.length > size) a[size] = null; return a; }
這兩個過載方法的區別就是,第一個會返回一個Object型別的陣列,第二個會返回一個指定型別的陣列.
remove(int index):刪除指定位置的元素
public E remove(int index) { //校驗 rangeCheck(index); modCount++; //被刪除的元素 E oldValue = elementData(index); //需要被複制的元素個數 int numMoved = size - index - 1; //如果被複制的元素個數大於0,那麼就複製一個數組 if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); //將底層陣列最後一個元素設定為null //讓GC開始工作 elementData[--size] = null; //返回被刪除的元素 return oldValue; }
如果被複制的元素葛素小於或者等於0,那麼意味著刪除的是末尾的元素,不需要變更元素位置.
remove(Object o):刪除指定元素
public boolean remove(Object o) { //判斷需要刪除的物件是否為null if (o == null) { //遍歷底層陣列,使用==null來尋找 for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { //如果不是null,是一個物件 //遍歷底層陣列,使用equals來尋找 for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } //如果沒有找到返回false return false; }
同樣是過載,其中fastRemove()方法和remove()邏輯基本一致,只是沒有返回值.並且被private修飾,是一個私有方法.
private void fastRemove(int index) { modCount++; // //需要被複制的元素個數 int numMoved = size - index - 1; //如果被複制的元素個數大於0,那麼就複製一個數組 if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); //將底層陣列最後一個元素設定為null //讓GC開始工作 elementData[--size] = null; // clear to let GC do its work }
clean():清楚集合內部元素,成為一個空集合
public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
這個應該一目瞭然了吧,就是迴圈遍歷底層陣列,將底層陣列所有的元素設定為null,並將size修改為0;