本篇主要介紹ArrayList的用法和源碼分析,基於jdk1.8,先從List接口開始。java
List接口定義了以下方法:數組
int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); boolean add(E e); void add(int index, E element); boolean addAll(Collection<? extends E> c); boolean addAll(int index, Collection<? extends E> c); boolean remove(Object o); E remove(int index); boolean removeAll(Collection<?> c); boolean retainAll(Collection<?> c); void clear(); E get(int index); E set(int index, E element); int indexOf(Object o); int lastIndexOf(Object o); List<E> subList(int fromIndex, int toIndex); ListIterator<E> listIterator(); ListIterator<E> listIterator(int index);
乍一看,這麼多方法。其實不少方法是一樣的功能,方法重載而已。
接下來逐個介紹下List定義的方法。dom
方法名言簡意賅,基本上均可以從方法名知道方法的目的。
接下來分析List的經常使用實現類:
ArrayList
LinkedList
Vectoride
本篇介紹ArrayList源碼分析
在個人理解中,ArrayList是一個封裝的數組,提供了一些便利的方法供使用者使用,規避了使用原生數組的風險。性能
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
繼承了AbstractList即成爲了List,就要實現定義的全部方法。
實現了RandomAccess接口 就是提供了隨機訪問能力,能夠經過下標得到指定元素
實現了Cloneable接口 表明是可克隆的,須要實現clone方法
實現了Serializable接口 表明ArrayList是可序列化的學習
下面介紹下ArrayList的主要方法ui
boolean add(E e)
先附上源碼this
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
此方法的執行邏輯:spa
private void ensureCapacityInternal(int minCapacity) { //是不是空List,是則使用初始容量擴容 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; //增長修改次數 // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1);//擴容1.5倍,採用位運算 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity);//最大容量就是Integer的最大值 // minCapacity is usually close to size, so this is a win: //採用Arrays的copyOf進行深拷貝,其中調用的本地方法System.arraycopy,此方法是在內存中操做所以速度會很快。 elementData = Arrays.copyOf(elementData, newCapacity); //至此擴容結束 }
void add(int index, E element)方法稍有不一樣
public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! //將index -> (size -1)的元素都日後移動一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
boolean addAll(Collection<? extends E> c)
boolean addAll(int index, Collection<? extends E> c)
這兩個方法和上面的add大同小異,第一步都是判斷容量,並擴容。
容量大小從1變爲c的length,elementData[index] = element;賦值也變爲數組拷貝,
直接上代碼,秒懂。
public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }
E remove(int index)
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }
boolean remove(Object o)
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }
這種方法依賴元素的equals方法,循環遍歷數組,由於ArrayList是容許null元素的,因此不管是使用if (o.equals(elementData[index])) 仍是 if (elementData[index].equals(o)) 都可能產生空指針,因此單獨對null進行處理,邏輯都是同樣的。
奇怪的是這個fastRemove方法,本來覺得會有些特殊處理,結果發現代碼和上面remove(int index)中的如出一轍,爲何上面的remove中不調用這個fastRemove呢?難道寫兩個remove方法的不是同一人?邏輯不影響,只是代碼冗餘了一點。
boolean removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, false); }
刪除存在於目標集合的方法,使用了batchRemove(Collection<?> c, boolean complement)這個方法,這個方法做了封裝,在retainAll(Collection<?> c)取並集這個方法也有使用。
retainAll中是這樣使用的:
return batchRemove(c, true);
和咱們的removeAll只差第二個參數boolean complement,remove是false,retail是true,那究竟是什麼意思呢?complement的原意的補充,在這裏我理解爲保留,remove就不保留,retail就保留,接着咱們分析batchRemove這個方法。
private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; int r = 0, w = 0; boolean modified = false; try { for (; r < size; r++) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; }
定義了 r 和 w兩個int,r用於遍歷原來的數組,w的意思是新的數組的size。
假如定義2個數組用於舉例,
數組1,五個元素 1,2,3,4,5
數組2,五個元素 3,4,6,7,8
數組1調用removeAll(數組2)
此時進入batchRemove,咱們來一步一步走看r,w如何變化
此時complement是false,即數組2中沒有數組1中第r個元素才知足if條件
首先執行
// Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; }
咱們的狀況是 r=size,那何時r會不等於size呢,jdk中寫了註釋,就是在if判斷時,調用數組2的contains方法,可能會拋空指針等異常。這時數組尚未遍歷完,那r確定是小於size的。
那沒判斷的那些數據還要不要處理?保守起見jdk仍是會將他保存在數組中,由於最終w是做爲新的size,因此w加上了沒處理過的個數size - r。
接着執行下面一段
if (w != size) { // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; }
這段代碼意圖很清晰,w由於是新的size值,因此將w及其以後的都置位null,增長修改次數,
給size賦予新值以後就結束了。
boolean contains(Object o)
contains 底層調用的是indexOf方法
public boolean contains(Object o) { return indexOf(o) >= 0; }
int indexOf(Object o)
public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; }
就是循環遍歷,一個個比對,有則返回對應下標,無則返回-1
對應的lastIndexOf是從最後往前遍歷。
public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; }
E get(int index)
rangeCheck(index); return elementData(index);
先檢查index,再返回數組對應元素。
E set(int index, E element)
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
先檢查index,在覆蓋index的元素,返回舊元素。
void clear()
modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0;
增長修改次數,將全部元素置空,size設置爲0,須要注意的是,數組的大小是沒有變化的。
int size()
size方法就是直接將size變量直接返回
public int size() { return size; }
boolean isEmpty()
判斷size是否等於0
public boolean isEmpty() { return size == 0; }
Iterator<E> iterator()
public Iterator<E> iterator() { return new Itr(); }
如代碼所示,建立了一個Itr對象並返回,接下來看Itr類的定義
private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
首先介紹下三個成員變量
日常經常使用的迭代器方法
hasNext()
就是判斷當前索引是否等於size。
E next()
首先檢查list是否被修改過,expectedModCount是在建立對象時就得到了,若是在以後對list進行了其餘修改操做的話,modCount就會增長,就會拋出ConcurrentModificationException。
沒有異常就按下表返回座標,cursor自增,lastRet也自增。
void remove()
首先判斷是否能刪除,若能則調用父類的remove方法,刪除元素,接着會更新cursor和lastRet。
最重要的是會更新expectedModCount,此時調用了父類的remove方法,會使modCount+1,因此更新了 expectedModCount,讓後續的檢查不會拋異常。
ListIterator<E> listIterator
listIterator和iterator同樣,只不過listIterator有更多的方法,至關於iterator的增強版。
定義以下
private class ListItr extends Itr implements ListIterator<E> { ListItr(int index) { cursor = index; } public boolean hasPrevious() { return cursor != 0; } public E previous() { checkForComodification(); try { int i = cursor - 1; E previous = get(i); lastRet = cursor = i; return previous; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public int nextIndex() { return cursor; } public int previousIndex() { return cursor-1; } public void set(E e) { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.set(lastRet, e); expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } public void add(E e) { checkForComodification(); try { int i = cursor; AbstractList.this.add(i, e); lastRet = -1; cursor = i + 1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } }
沒有什麼須要注意的地方,基本的方法都從Iterator中繼承過來並添加一些方法。
List<E> subList(int fromIndex, int toIndex)
這又是一個內部類
class SubList<E> extends AbstractList<E> { private final AbstractList<E> l; private final int offset; private int size; SubList(AbstractList<E> list, int fromIndex, int toIndex) { if (fromIndex < 0) throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); if (toIndex > list.size()) throw new IndexOutOfBoundsException("toIndex = " + toIndex); if (fromIndex > toIndex) throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); l = list; offset = fromIndex; size = toIndex - fromIndex; this.modCount = l.modCount; } 。 。 。 }
持有了父類的引用,內部的全部方法都是經過父類的這個引用去完成的,
因此須要注意的是,subList不是返回一個新的List,仍是原來的引用,因此改變subList的數據,原有的數據也會更改。
終於把基本的方法都介紹完了,從源碼的角度分析了全部的方法,感受對ArrayList知根知底了,用它時確定會更加駕輕就熟了,看了源碼纔有原來實現都這麼簡單啊這樣的感受,不過從中也學習到了大牛規範的代碼風格,良好的結構,可讀性很高。下篇分析List的另外一個實現類,LinkedList。