Java集合-ArrayList源碼解析-JDK1.8


ArrayList簡介
java

ArrayList 是一個數組隊列,至關於 動態數組。與Java中的數組相比,它的容量能動態增加。它繼承於AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些接口。git

  1. AbstractList、List提供了添加、刪除、修改、遍歷等功能。
  2. RandmoAccess提供了隨機訪問功能
  3. Cloneable提供了能夠被克隆的功能
  4. Serializable提供了序列化的功能
  5. 和Vector不一樣,ArrayList中的操做不是線程安全的!因此,建議在單線程中才使用ArrayList,而在多線程中能夠選擇Vector或CopyOnWriteArrayList。


ArrayList的屬性
github

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的幾個比較經典的方法來看一下它是如何設計的。bash

首先是添加方法,添加的方法一共有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+集合的長度。dom

接着看這個方法的實現:學習

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動態擴容的實現方式了,這裏注意一下擴容是經過新建一個數組來替換原先的數組來進行的:ui

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

相關文章
相關標籤/搜索