做爲一個老碼農,不只要談架構、談併發,也不能忘記最基礎的語言和數據結構,所以特開闢這個系列的文章,爭取每月寫1~2篇關於java基礎知識的文章,以溫故而知新。java
如無特別之處,這個系列文章所使用的java版本都是1.8.0。數組
第一篇固然談ArrayList了,由於這是java最經常使用的list集合類型,它內部使用數組做爲存儲空間,在增長元素時可以自動增加。整體來講,ArrayList的實現比較簡單,這裏不羅列它的所有代碼,只看一些有意思的地方。安全
private static final int DEFAULT_CAPACITY = 10; private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Object[] elementData; private int size;
PS:說實話,DEFAULTCAPACITY_EMPTY_ELEMENTDATA的使用到底帶來了什麼好處,我沒想明白。數據結構
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this.elementData = EMPTY_ELEMENTDATA; } }
private void ensureCapacityInternal(int minCapacity) { 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); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
這是三個私有方法多線程
這裏有一個疑問,ensureExplicitCapacity方法的那句modCount++
爲何會在if語句外執行.架構
基本操做方法有不少,這裏只列幾個有表明性的。併發
一、查找:dom
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; }
ArrayList查找元素的過程就是遍歷數組,並且null也是能夠查找的。函數
二、插入:ui
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; size++; }
插入前作了範圍檢查,並確保容量,最後把插入位置以後的元素所有再日後挪一個位置;移動內存使用了System.arraycopy
,這是一個native方法。
值得注意的是,add方法並無直接調用modCount++,由於ensureCapacityInternal裏面調用了,因此我猜想modCount++放在ensureCapacityInternal裏面純碎就是爲了更方便,即便有些沒有修改數組的場景也會致使modCount被修改。
三、刪除:
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; }
把刪除位置以後的元素所有往前挪一個位置,注意的是,最後空出來的那個內存位置要置成null,不然會內存泄露。
四、批量刪除
public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, false); } public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true); } 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; }
removeAll刪除集合參數裏的全部元素,反過來retainAll刪除不在集合參數裏面的全部元素,兩者都經過batchRemove來實現。
batchRemove使用兩個遊標w,r來執行連續的元素移動,w表明寫入位置,r表明讀取位置,讀取一個元素後判斷是否應該刪除,若是是那麼繼續讀下一個,不然寫入w位置。 這樣,到了最後w位置就是list的末尾,w~r之間的位置須要置成null。
值得注意的是,最後的收尾邏輯放到finally裏面,保證了必定程度的異常安全性。若是發生異常,那麼剩餘未掃描的元素(r位置以後的元素),要拷貝到w以後;這樣一來,batchRemove可能執行了一半而失敗,但ArrayList的狀態並無亂掉。
ArrayList支持兩種迭代器,Iterator和ListIterator,後者是前者的加強版本,能夠向前移動、插入元素、返回元素索引。
這裏就解讀下Iterator實現,ListIterator是差很少的。
一、Iterator成員變量
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; } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
Iterator實現爲ArrayList的非靜態內部內,所以能夠直接訪問ArrayList的成員字段,它只須要記住當前位置(cursor)便可。lastRet指向上一次next操做返回的元素位置,這個是很是有必要的,由於在迭代器上,若是要對元素作一些操做,都是針對這個位置的元素。
expectedModCount是ArrayListmodCount的快照,防止在迭代過程當中,意外對list執行修改。
二、next操做
@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]; }
很簡單,幾乎沒啥可解釋的。
三、remove操做
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(); } }
remove操做會更新expectedModCount,這是一個容許的、意料之中的修改操做。
四、modCount
modCount定義在基類AbstractList當中,使用來追蹤list修改的,自身的值沒有實際意義。
modCount主要最用,就是在發生意外的修改時,快速失敗,而不是等到出現難以追蹤的數據狀態錯誤時才失敗。思路是,若是在執行一個操做的過程當中,若是不指望ArrayList被其餘操做所修改,那麼能夠在開始時記錄一下modCount快照,在執行操做的過程當中,經過比較ArrayList的modCount和這個快照來斷定ArrayList是否被修改了,而後拋出ConcurrentModificationException。
ConcurrentModificationException看起來像是多線程相關的,但實際上這裏和多線程一點關係都沒有,ArrayList不是線程安全的,modCount的設計也沒有針對多線程的意思。
ArrayList的subList能夠直接經過遊標鎖定原ArrayList的一個區段,從而避免了copy。
因爲SubList要實現List的所有接口,所有源碼比較多,這裏講解一下get方法的實現,其餘的都是相似的。
private class SubList extends AbstractList<E> implements RandomAccess { private final AbstractList<E> parent; private final int parentOffset; private final int offset; int size; SubList(AbstractList<E> parent, int offset, int fromIndex, int toIndex) { this.parent = parent; this.parentOffset = fromIndex; this.offset = offset + fromIndex; this.size = toIndex - fromIndex; this.modCount = ArrayList.this.modCount; } public E get(int index) { rangeCheck(index); checkForComodification(); return ArrayList.this.elementData(offset + index); } public E set(int index, E e) { rangeCheck(index); checkForComodification(); E oldValue = ArrayList.this.elementData(offset + index); ArrayList.this.elementData[offset + index] = e; return oldValue; } }
一、首先,構造函數能夠看出來,SubList實際就是經過索引來操做原ArrayList的數據,只不過加一個偏移(offset)和長度限制(size)。
二、get方法檢查modCount,說明在SubList的生命週期內,指望parent ArrayList不會被修改。這也說明SubList只適合作臨時變量,不適合長期存活,除非原ArrayList是不變的。
AbstractList是ArrayList的基類,它也實現了Iterator,ListIterator,SubList的一個版本。不過AbstractList並不清楚存儲的實現細節,所以只能基於list的公共接口來實現,所以效率確定不佳。
好比AbstractList的Iterator的next方法是這樣實現的:
public E next() { checkForComodification(); try { int i = cursor; E next = get(i); lastRet = i; cursor = i + 1; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } }
獲取元素用的是list.get方法:E next = get(i)
。