// 當前數據對象存放地方,當前對象不參與序列化 // 這個關鍵字最主要的做用就是當序列化時,被transient修飾的內容將不會被序列化 transient Object[] elementData;
Object類型數組。java
// 序列化ID private static final long serialVersionUID = 8683452581122892189L; // 默認初始容量 private static final int DEFAULT_CAPACITY = 10; // 一個空數組,方便使用,主要用於帶參構造函數初始化和讀取序列化對象等。 private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 和官方文檔寫的同樣,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 和EMPTY_ELEMENTDATA 的區別 * 僅僅是爲了區別用戶帶參爲0的構造和默認構造的惰性初始模式對象。 * 當用戶帶參爲0的構造,第一次add時,數組容量grow到1。 * 當用戶使用默認構造時,第一次add時,容量直接grow到DEFAULT_CAPACITY(10)。 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 當前數據對象存放地方,當前對象不參與序列化 // 這個關鍵字最主要的做用就是當序列化時,被transient修飾的內容將不會被序列化 transient Object[] elementData; // non-private to simplify nested class access // 當前數組中元素的個數 private int size; // 數組最大可分配容量 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 集合數組修改次數的標識(由AbstractList繼承下來)(fail-fast機制) protected transient int modCount = 0;
public ArrayList() { // 只有這個地方會引用DEFAULTCAPACITY_EMPTY_ELEMENTDATA this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { // 使用 EMPTY_ELEMENTDATA,在其餘的多個地方可能會引用EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } public ArrayList(Collection<? extends E> c) { // 把傳入集合傳化成[]數組並淺拷貝給elementData elementData = c.toArray(); // 轉化後的數組長度賦給當前ArrayList的size,並判斷是否爲0 if ((size = elementData.length) != 0) { //c.toArray可能不會返回 Object[],能夠查看 java 官方編號爲 6260652 的 bug if (elementData.getClass() != Object[].class) // 若 c.toArray() 返回的數組類型不是 Object[],則利用 Arrays.copyOf(); 來構造一個大小爲 size 的 Object[] 數組 // 此時elementData是指向傳入集合的內存,還須要建立新的內存區域深拷貝給elementData elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 傳入數組size爲零替換空數組 this.elementData = EMPTY_ELEMENTDATA; } }
帶參爲0的構造會惰性初始化,不爲0的構造則不會惰性初始化。數組
public boolean add(E e) { // 確保數組已使用長度(size)加1以後足夠存下 下一個數據 ensureCapacityInternal(size + 1); // Increments modCount!! // 數組的下一個index存放傳入元素。 elementData[size++] = e; // 始終返回true。 return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { // 這裏就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA 和 // EMPTY_ELEMENTDATA 最主要的區別。 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 默認構造第一次add返回10。 return Math.max(DEFAULT_CAPACITY, minCapacity); } // 帶參爲0構造第一次add返回 1 (0 + 1)。 return 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; // 新數組容量爲1.5倍的舊數組容量 int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) // 若 newCapacity 依舊小於 minCapacity newCapacity = minCapacity; // 判斷是須要的容量是否超過最大的數組容量。 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: // 在Arrays.copyOf()中會將原數組整個賦值到擴容的數組中。 elementData = Arrays.copyOf(elementData, newCapacity); }
// 這是一個本地方法,由C語言實現。 public static native void arraycopy(Object src, // 源數組 int srcPos, // 源數組要複製的起始位置 Object dest, // 目標數組(將原數組複製到目標數組) int destPos, // 目標數組起始位置(從目標數組的哪一個下標開始複製操做) int length // 複製源數組的長度 ); public void add(int index, E element) { // 判斷索引是否越界 rangeCheckForAdd(index); // 確保數組已使用長度(size)加1以後足夠存下 下一個數據 ensureCapacityInternal(size + 1); // Increments modCount!! // 運行到這裏表明數組容量知足。 // 數組從傳入形參index處開始複製,複製size-index個元素(即包括index在內後面的元素所有複製), // 從數組的index + 1處開始粘貼。 // 這時,index 和 index + 1處元素數值相同。 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 把index處的元素替換成新的元素。 elementData[index] = element; // 數組內元素長度加一。 size++; }
public E remove(int index) { // 檢查index rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) // 和 add(int index, E element)原理想通。 System.arraycopy(elementData, index+1, elementData, index, numMoved); // 引用計數爲0,會自動進行垃圾回收。 elementData[--size] = null; // clear to let GC do its work // 返回舊元素 return oldValue; }
fail-fast 機制,即快速失敗機制,是java集合(Collection)中的一種錯誤檢測機制。當在迭代集合的過程當中該集合在結構上發生改變的時候,就有可能會發生fail-fast,即拋出ConcurrentModificationException異常。fail-fast機制並不保證在不一樣步的修改下必定會拋出異常,它只是盡最大努力去拋出,因此這種機制通常僅用於檢測bug。安全
private class Itr implements Iterator<E> { int cursor; int lastRet = -1; // 期待的修改值等於當前修改次數(modCount) int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } public E next() { // 檢查 expectedModCount是否等於modCount,不相同則拋出ConcurrentModificationException checkForComodification(); /** 省略此處代碼 */ } public void remove() { if (this.lastRet < 0) throw new IllegalStateException(); checkForComodification(); /** 省略此處代碼 */ } final void checkForComodification() { if (ArrayList.this.modCount == this.expectedModCount) return; throw new ConcurrentModificationException(); } }
一個單線程環境下的fail-fast的例子多線程
public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0 ; i < 10 ; i++ ) { list.add(i + ""); } Iterator<String> iterator = list.iterator(); int i = 0 ; while(iterator.hasNext()) { if (i == 3) { list.remove(3); } System.out.println(iterator.next()); i ++; } }
ArrayList 實現了 java.io.Serializable 接口,可是本身定義了序列化和反序列化。由於ArrayList基於數組實現,而且具備動態擴容特性,所以保存元素的數組不必定都會被使用,那麼就沒有必要所有進行序列化。所以 elementData 數組使用 transient 修飾,能夠防止被自動序列化。併發
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; // 將當前類的非靜態(non-static)和非瞬態(non-transient)字段寫入流 // 在這裏也會將size字段寫入。 s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone() // 序列化數組包含元素數量,爲了向後兼容 // 兩次將size寫入流 s.writeInt(size); // Write out all elements in the proper order. // 按照順序寫入,只寫入到數組包含元素的結尾,並不會把數組的全部容量區域所有寫入 for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } // 判斷是否觸發Fast-Fail if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // 設置數組引用空數組。 elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff // 將流中的的非靜態(non-static)和非瞬態(non-transient)字段讀取到當前類 // 包含 size s.defaultReadObject(); // Read in capacity // 讀入元素個數,沒什麼用,只是由於寫出的時候寫了size屬性,讀的時候也要按順序來讀 s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity // 根據size計算容量。 int capacity = calculateCapacity(elementData, size); // SharedSecrets 一個「共享機密」存儲庫,它是一種機制, // 用於調用另外一個包中的實現專用方法,而不使用反射。TODO SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity); // 檢查是否須要擴容 ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. // 依次讀取元素到數組中 for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }
ArrayList中爲何size要序列化兩次?dom
在代碼中s.defaultWriteObject();中size應該也被序列化了,爲何下邊還要再單獨序列化一次呢?
這樣寫是出於兼容性考慮。
舊版本的JDK中,ArrayList的實現有所不一樣,會對length字段進行序列化。
而新版的JDK中,對優化了ArrayList的實現,再也不序列化length字段。
這個時候,若是去掉s.writeInt(size),那麼新版本JDK序列化的對象,在舊版本中就沒法正確讀取,
由於缺乏了length字段。
所以這種寫法看起來畫蛇添足,實際上卻保證了兼容性。函數