1. 程式人生 > >ArrayList源碼剖析

ArrayList源碼剖析

第一個 算法 創建 nts 驗證 查詢 span 源碼剖析 list

ArrayList為List的一個實現類,List的實現類有很多,我們該選擇在什麽時候使用什麽集合?需要對他們有一個深入的了解.

1.構造方法,這裏我們介紹兩個常用的,第一個當屬我們的空參構造方法

public ArrayList() {

  super();

  this.elementData = EMPTY_ELEMENTDATA;
}

  這裏,ArrayList(後續我會稱之為本類)中管理了一個常量EMPTY_ELEMENTDATA,這個常量聲明是這樣的:

private static final Object[] EMPTY_ELEMENTDATA = {};

  這是他內部管理的一個空的對象數組,而elementData的聲明同樣也是一個對象數組,具體如下:

private transient Object[] elementData;

  這裏的elementData是類中真實存放數據的一個對象數組,即後續存入數據真正是存放在此對象數組中,由於其類型為Object,所以可以存入所有類型的對象.基於此,其實我們就可以大膽猜測其實ArrayList內部就是基於對象數組實現的,(還有一個構造方法我們後邊再給大家引入)那麽它就具備了數組的一些特性:存取有序,查詢方便,插入刪除不便.那麽為什麽這麽說,接下來我們深入剖析一下他的增刪改查方法;

  增加:

public boolean add(E e) {

  ensureCapacityInternal(size + 1); // Increments modCount!!
  elementData[size++] = e;
  return true;
}

  如上添加時傳入了一個參數,此處為泛型,這裏就表示可以傳入所有類型的參數,內部的實現如上,可能大家都比較想知道的是數組不是必須在初始化的時候就給定長度麽?數組不是固定長度麽?我們使用集合存入數據時是可以一直存入的昂,具體是如何實現的?此處我們需要關註一下上面的ensureCapacityInternal(size + 1)這個方法,就是他實現了數組的動態擴充,也就是數組會隨著元素的添加而不斷的擴充,我們去看下他的具體實現:

private void ensureCapacityInternal(int minCapacity) {

  if (elementData == EMPTY_ELEMENTDATA) {
    minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  }

  ensureExplicitCapacity(minCapacity);
}

  上面的方法開始就做了一個簡單的驗證,判斷真實存放數據的數組是否等於空的對象數組,如果為空,則真實存放數據的elementData長度為10.

private static final int DEFAULT_CAPACITY = 10;

//下面是Math.max(a,b)方法的實現,目的是比較二者獲取一個大值

public static int max(int a, int b) {
  return (a >= b) ? a : b;
}

  接下來執行了一個

private void ensureExplicitCapacity(int minCapacity) {

  modCount++;

  // overflow-conscious code
  if (minCapacity - elementData.length > 0)
    grow(minCapacity);
}

  根據存入數據的下標+1來表示當前數據實際存放的數量,從而來判斷數據是否已經存滿,需要擴充,如果存入數據數量+1減去已有數據數組長度大於零,即執行下面的grow方法

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);
}

  如上,先獲取到現在對象數組的長度,即oldCapacity,再通過算法得到新的數組長度:oldCapacity + (oldCapacity >> 1),這裏算法實現是原數組長度+原數組向右位移一位,即原數組長度乘以2,因為位移運算效率高,所以此處這麽寫,相當於oldCapacity +oldCapacity *2.後續兩個判斷是邏輯性的驗證,最後利用Arrays.copyOf方法,根據原數組和傳入新數組的長度來得到新的數組,並賦值給了elementData .這裏我們要追進去Arrays類中去看下具體是如何實現的:

public static <T> T[] copyOf(T[] original, int newLength) {
  return (T[]) copyOf(original, newLength, original.getClass());
}

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {

  T[] copy = ((Object)newType == (Object)Object[].class)
  ? (T[]) new Object[newLength]
  : (T[]) Array.newInstance(newType.getComponentType(), newLength);
  System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));
  return copy;
}

  有些新手看到這可能已經被一系列的T[],U[]給整蒙了, 這裏具體實現是判斷利用反射對存入類型做了最後一步校驗,如果不是同一類型,則以傳入的新長度為長度,直接構建一個新的對象數組並返回,如果是則調用系統的arraycopy方法,將舊的數組中元素依次有序的存入新數組中,並將新數組返回.至於System.arraycopy方法,就是循環將舊數組中的元素依次存入新數組中,由於System.arraycopy方法已經封裝為native方法,無法查看.

public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);

  到這裏,已經可以看出來添加方法,其實核心是一個數組的動態擴充,即丟棄舊的數組,創建新的數組,所以如果頻繁的擴充對象數組,就會制造大量垃圾,浪費內存,影響性能,此時,我們如果已知存放數據大致數量的情況下,我們即可以使用第二種構造方法:指定初始化容量,減少內存的大量消耗

public ArrayList(int initialCapacity) {

  super();
  if (initialCapacity < 0)
  throw new IllegalArgumentException("Illegal Capacity: "+
  initialCapacity);
  this.elementData = new Object[initialCapacity];
}

  後續明天再擼...

ArrayList源碼剖析