JDK1.8中ArrayList的實現原理及原始碼分析
一、概述
ArrayList是Java開發中使用比較頻繁的一個類,通過對原始碼的解讀,可以瞭解ArrayList的內部結構以及實現方法,清楚它的優缺點,以便我們在程式設計時靈活運用。
二、原始碼分析
2.1 類結構
JDK1.8原始碼中的ArrayList類結構定義如下:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
其中:
- 實現了List介面是一個數組佇列擁有了List基本的增刪改查功能
- 實現了RandomAccess介面擁有隨機讀寫的功能
- 實現了Cloneable介面可以被克隆
- 實現了Serializable介面並重寫了序列化和反序列化方法,使得ArrayList可以擁有更好的序列化的效能
2.2 成員變數和幾個構造方法
/** * 定義序列化ID,主要是為了表示不同的版本的相容性 */ private static final long serialVersionUID = 8683452581122892189L; /** * 預設的陣列儲存容量(ArrayList底層是陣列結構) */ private static final int DEFAULT_CAPACITY = 10; /** * 當指定陣列的容量為0時使用這個常量賦值 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 預設空參建構函式時使用這個常量賦值 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 真正存放資料的物件陣列,transient標識不被序列化 */ transient Object[] elementData; // non-private to simplify nested class access /** * 陣列中的真實元素個數,該值小於或等於elementData.length */ private int size; /** * 修改次數 */ protected transient int modCount = 0; /** * 建構函式一:指定了容量的大小 * @param initialCapacity */ 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; } /** * 建構函式三:傳入集合引數的建構函式 * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection<? extends E> c) { elementData = ((ArrayList<?>) 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; } }
2.3 常用方法
- 往ArrayList中加入元素:add(E e)以及相關方法
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { /** * 如果原來的空陣列,則比較加入的個數與默認個數(10)比較,取較大值 */ if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; /** * 判斷陣列真實元素個數加1後的長度與當前陣列長度大小關係,如果小於0,返回,如果大於0,則 * 呼叫grow(minCapacity)方法 */ if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1);//容量變成原來的1.5倍 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); } public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @SuppressWarnings("unchecked") 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; }
分析:
- ensureCapacityInternal(size+1)方法,在該方法中首先判斷了當前陣列是否是空陣列,如果是則比較加入的個數與默認個數(10)比較,取較大值,否則呼叫2方法。
- ensureExplicitCapacity(int minCapacity)方法,在該方法中首先是對modCount+1,判斷陣列真實元素個數加1後的長度與當前陣列長度大小關係,如果小於0,返回,如果大於0,則呼叫3方法。
- grow(minCapacity)方法,使用 oldCapacity + (oldCapacity >> 1)是當前陣列的長度變為原來的1.5倍,再與擴容後的長度以及擴容的上限值進行對比,然後呼叫4方法。
- Arrays.copyOf(elementData, newCapacity)方法,該方法的底層就是呼叫System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength))方法,把舊資料的資料拷貝到擴容後的新數組裡面,返回新陣列 - 然後再把新新增的元素賦值給擴容後的size+1的位置裡面。
- 往指定位置插入元素add(int idnex,E element)
原始碼如下:
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++;
}
從原始碼中可以看出,與add(E e)方法大致一致,主要的差異是增加了一行程式碼:System.arraycopy(elementData, index, elementData, index + 1, size - index),從index位置開始以及之後的資料,整體拷貝到index+1開始的位置,然後再把新加入的資料放在index這個位置,而之前的資料不需要移動。(這些動作比較消耗效能)
java.lang.System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length),引數含義如下:
(原陣列,原陣列的開始位置,目標陣列,目標陣列的開始位置,拷貝的個數)
- 移除(根據下標移除和根據元素移除)
原始碼如下:
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);
//把size-1的位置的元素賦值為null,方便GC回收
elementData[--size] = null;
return oldValue;
}
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;
}
remove方法與add正好是一個相反的操作,移除一個元素,會影響到一批數字的位置移動,所以也是比較耗效能。核心程式碼都是呼叫了java.lang.System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)方法
- 查詢
public E get(int index) {
/**
* 檢查是否越界
*/
rangeCheck(index);
/**
* 返回指定位置上的元素
*/
return elementData(index);
}
// 位置訪問操作
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
- 修改
public E set(int index, E element) {
/**
* 檢查是否越界
*/
rangeCheck(index);
/**
* 獲取舊的元素值
*/
E oldValue = elementData(index);
/**
* 新元素賦值
*/
elementData[index] = element;
/**
* 返回舊的元素值
*/
return oldValue;
}
- 清空方法
public void clear() {
modCount++;
// 將每個元素至為null,便於gc回收
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
- 是否包含
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
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;
}
該方法分兩種情況:null值和非null的遍歷,如果查詢到就返回下標位置,否則就返回-1,然後與0比較,大於0就存在,小於0就不存在。
三、總結
基於陣列實現的List在隨機訪問和遍歷的效率比較高,但是往指定位置加入元素或者刪除指定位置的元素效率比較低。