Java集合-ArrayList原始碼解析-JDK1.8

Java學習錄發表於2019-04-02


ArrayList簡介

ArrayList 是一個陣列佇列,相當於 動態陣列。與Java中的陣列相比,它的容量能動態增長。它繼承於AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些介面。

  1. AbstractList、List提供了新增、刪除、修改、遍歷等功能。
  2. RandmoAccess提供了隨機訪問功能
  3. Cloneable提供了可以被克隆的功能
  4. Serializable提供了序列化的功能
  5. 和Vector不同,ArrayList中的操作不是執行緒安全的!所以,建議在單執行緒中才使用ArrayList,而在多執行緒中可以選擇Vector或CopyOnWriteArrayList。


ArrayList的屬性

1234567891011121314151617181920212223242526272829303132複製程式碼
/** * 陣列預設的大小 */
private static final int DEFAULT_CAPACITY = 10;
/** * 使用陣列大小為0時的預設緩衝區 */
private static final Object[] EMPTY_ELEMENTDATA = {};
/** * 使用ArrayList(int initialCapacity)構造方法時且initialCapacity為0時緩衝區 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/** * 真實儲存arraylist元素的陣列緩衝區 */
transient Object[] elementData; 
// non-private to simplify nested class access
/** * List的實際大小 */
private int size;
/** * 陣列可分配的最大大小 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 
/** *  特別注意這個是繼承自AbstractList的屬性,用來記錄List被修改的次數 */
protected transient int modCount = 0;複製程式碼


ArrayList的構造方法

1234567891011121314151617181920212223242526272829303132333435複製程式碼
 /** * 無參構造方法,初始化elementData */
public ArrayList() { 
   this.elementData = 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); 
   }}/** * 建立一個包含collection的ArrayList */
public ArrayList(Collection<? extends E> c) { 
   elementData = c.toArray();  
  if ((size = elementData.length) != 0) {  
      // c.toArray might (incorrectly) not return Object[] (see 6260652)    
    if (elementData.getClass() != Object[].class)      
      elementData = Arrays.copyOf(elementData, size, Object[].class);  
  } else {        //replace with empty array.     
   this.elementData = EMPTY_ELEMENTDATA; 
   }}複製程式碼


ArrayList的方法

接下來我們就以ArrayList的幾個比較經典的方法來看一下它是如何設計的。

首先是新增方法,新增的方法一共有3個:

123456789101112131415161718192021222324252627282930313233343536複製程式碼
/**    * 新增元素    */   public boolean add(E e) {       //計算陣列最新的容量,以及判斷是否需要擴容       ensureCapacityInternal(size + 1);  // Increments modCount!!       elementData[size++] = e;       return true;   }   /**    * 指定索引新增元素    */   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;       //List長度+1       size++;   }    /**    * 新增一個集合    */   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;   }複製程式碼

仔細觀察上方三個新增的方法,它們都呼叫了ensureCapacityInternal方法,這個方法的引數是執行當前新增操作所需要的陣列容量。它會根據傳遞的引數來計算陣列是否需要擴容,如果需要擴容則完成擴容操作。
不同之處在於,上方的兩個方法新增的只有一個元素,所以傳的size+1,而addAll因為是新增的一個集合所以傳的引數是size+集合的長度。

接著看這個方法的實現:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152複製程式碼
/**    * 計算陣列最新的容量    * @param minCapacity    */   private void ensureCapacityInternal(int minCapacity) {       //如果建立ArrayList時指定大小為0       if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {           //如果本次新增的大小比初始容量10大的話則不使用預設的容量10,直接使用本次新增的大小作為初始容量           minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);       }       ensureExplicitCapacity(minCapacity);   }   /**    * 記錄修改次數,呼叫擴容方法    * @param minCapacity    */   private void ensureExplicitCapacity(int minCapacity) {       modCount++;       // overflow-conscious code       if (minCapacity - elementData.length > 0)           //擴容           grow(minCapacity);   }   /**    * 擴容    */   private void grow(int minCapacity) {       // 獲取原來的陣列長度       int oldCapacity = elementData.length;       //新容量設定為老容量的1.5倍       int newCapacity = oldCapacity + (oldCapacity >> 1);       //如果新容量還不夠存放本次需要新增的大小,則直接擴容到本次新增的大小       if (newCapacity - minCapacity < 0)           newCapacity = minCapacity;       //如果新容量超出陣列最大容量       if (newCapacity - MAX_ARRAY_SIZE > 0)           newCapacity = hugeCapacity(minCapacity);       // 呼叫Arrays的複製方法更新資料緩衝池       elementData = Arrays.copyOf(elementData, newCapacity);   }     //判斷容量是否溢位   private static int hugeCapacity(int minCapacity) {       if (minCapacity < 0) // overflow           throw new OutOfMemoryError();       return (minCapacity > MAX_ARRAY_SIZE) ?               Integer.MAX_VALUE :               MAX_ARRAY_SIZE;   }複製程式碼

以上就是ArrayList動態擴容的實現方式了,這裡注意一下擴容是通過新建一個陣列來替換原先的陣列來進行的:

1複製程式碼
elementData = Arrays.copyOf(elementData, newCapacity);複製程式碼

接下來看刪除操作:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152複製程式碼
/**    * 遍歷陣列,找出需要刪除的元素的索引,並呼叫刪除方法    */   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;   }/**    * 刪除指定索引的元素    *    */   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;   }   /*    * 刪除指定元素    */   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   }複製程式碼

需要注意的是刪除一個元素也是通過底層的方法實現的。

接著看get和set相對就比較簡單了。

1234567891011121314151617181920複製程式碼
public E get(int index) {       //判斷索引是否越界       rangeCheck(index);       return elementData(index);   }    /**    * 判斷索引是否越界    */   private void rangeCheck(int index) {       if (index >= size)           throw new IndexOutOfBoundsException(outOfBoundsMsg(index));   }   public E set(int index, E element) {       //判斷索引是否越界       rangeCheck(index);       //獲取此索引原先的值       E oldValue = elementData(index);       elementData[index] = element;       return oldValue;   }複製程式碼

看了ArrayList的增刪改查方法相信你已經明白了為什麼一直有人告訴你ArrayList查詢修改效率高而新增和刪除效率低了。

ArrayList的序列化方式同樣是比較有意思的,一開始看到ArrayList實現了Serializable我們就知道它是可以序列化的,但是實際儲存的陣列elementData卻是transient,觀看下方程式碼你就可以找到答案:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748複製程式碼
/**   * 將List寫入s,注意先寫容量,然後在寫資料   * @param s   * @throws java.io.IOException   */  private void writeObject(java.io.ObjectOutputStream s)          throws java.io.IOException{      // Write out element count, and any hidden stuff      int expectedModCount = modCount;      s.defaultWriteObject();      // 首先寫陣列容量      s.writeInt(size);      // 遍歷寫陣列中的元素      for (int i=0; i<size; i++) {          s.writeObject(elementData[i]);      }      if (modCount != expectedModCount) {          throw new ConcurrentModificationException();      }  }  /**   * 讀取s中的List   */  private void readObject(java.io.ObjectInputStream s)          throws java.io.IOException, ClassNotFoundException {      elementData = EMPTY_ELEMENTDATA;      // Read in size, and any hidden stuff      s.defaultReadObject();      // 首先讀陣列容量      s.readInt(); // ignored      if (size > 0) {          // be like clone(), allocate array based upon size not 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完整原始碼解析請看:github.com/shiyujun/sy…!!!

部落格所有文章首發於公眾號《Java學習錄》轉載請保留 掃碼關注公眾號即可領取2000GJava學習資源

1


相關文章