ArrayList源碼理解
導讀
與LinkedList源碼理解放在一起查閱,效果更好
本文先給出ArrayList的特點,再從源碼的角度分析爲什麼會有這些特點:
- 對隊成員變量的分析,可以知道ArrayList的數據結構
- 對add()方法的分析,可以得知ArrayList添加數據的效率不高
- 對get()方法的分析,可以看出ArrayList查詢的效率非常高
- 對remove()方法的分析,可以瞭解到ArrayList刪除數據的效率不高
特點
與LinkedList比較,ArrayList有以下特點:
- 查詢效率高
- 添加和刪除的效率不高
- 數據結構是數組
源碼分析
成員變量
從成員變量中可以看出,ArrayList是用數組Object[]來儲存數據的
//容器默認大小
private static final int DEFAULT_CAPACITY = 10;
//空的容器
private static final Object[] EMPTY_ELEMENTDATA = {};
//容器,用來儲存數據
private transient Object[] elementData;
//標記容器的大小
private int size;
構造方法
三個重寫的構造方法都是用來初始化容器
//構造方法
public ArrayList(int initialCapacity) {
super();
//非法參數校驗
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//給定數組的大小
this.elementData = new Object[initialCapacity];
}
//構造方法
public ArrayList() {
super();
//默認爲空數組
this.elementData = EMPTY_ELEMENTDATA;
}
//構造方法
public ArrayList(Collection<? extends E> c) {
//將給定的集合轉換成數組
elementData = c.toArray();
//此時,容器內有數據,更新size
size = elementData.length;
//c.toArray()出現異常的處理
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
add()的分析
從下面的代碼中可以看出,ArrayList是通過數組賦值的方式來添加數據
//添加數據
public boolean add(E e) {
//更新一些數據(modCount),根據一些情況來擴充容器的大小
ensureCapacityInternal(size + 1);
//添加數據,更新size
elementData[size++] = e;
return true;
}
//向指定的位置添加數據
public void add(int index, E element) {
//非法參數校驗
rangeCheckForAdd(index);
//更新一些數據(modCount),根據一些情況來擴充容器的大小
ensureCapacityInternal(size + 1);
//從index開始(包括),數據向後移動一位(這裏的效率低)
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//添加數據
elementData[index] = element;
//更新size
size++;
}
從下面的代碼中可以看出數組的大小是動態變化的,一般擴充倍數爲1.5倍
//根據minCapacity來確定容器的大小
private void ensureCapacityInternal(int minCapacity) {
//當前爲空容器的處理
if (elementData == EMPTY_ELEMENTDATA) {
//minCapacity取最大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//更新modCount
modCount++;
// 如果需要的容量大於當前的容量,擴充容器
if (minCapacity - elementData.length > 0)
//擴中容器的大小
grow(minCapacity);
}
//ARRAY的最大值
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//擴中容器
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);
// 數據複製到新容器中,(這裏效率不高)
elementData = Arrays.copyOf(elementData, newCapacity);
}
//處理容量巨大時的情況
private static int hugeCapacity(int minCapacity) {
//超過int的最大值,拋出異常
if (minCapacity < 0)
throw new OutOfMemoryError();
//根據情況設置爲int的最大值或者MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
說明:ArrayList在添加數據的時,有時候會移動大量的數據,導致添加數據效率不高。
get()的分析
從下面的代碼中可以看出,ArrayList在查找數據時,通過index直接鎖定內存,可以迅速地找到相應的數據
//獲取指定位置上的數據
public E get(int index) {
//非法參數校驗
rangeCheck(index);
//返回數據
return elementData(index);
}
remove()的分析
移除指定位置上的數據:remove(int index)
//移除指定位置上的數據
public E remove(int index) {
//非法參數校驗
rangeCheck(index);
//更新modCount
modCount++;
//獲取指定的數據
E oldValue = elementData(index);
//計算要移動數據的個數
int numMoved = size - index - 1;
//根據情況去移動數據
if (numMoved > 0)
//從index+1開始,所有數據向前移動一位,這樣,index位置上的數據就被覆蓋了,相當於移除了
//這裏可以看出,移除操作的效率不高
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//容器的最後一位置空,更新size
elementData[--size] = null;
return oldValue;
}
說明:
1. 移除操作實際上是一個覆蓋數據的操作,是將後面一個數據覆蓋到前一個位置,如果是末尾的數據,則直接置空。
2. 與add()中的耗時操作類似:可能移動大量的數據導致低效率
移除特定的數據:remove(Object o)
//移除容器中第一個相應的數據
public boolean remove(Object o) {
//首先對o進行空判斷,防止o.equal()報錯
//兩部操作的邏輯一致
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;
}
//移除指定位置上的數據
private void fastRemove(int index) {
//更新modCount
modCount++;
//計算要移動數據的個數
int numMoved = size - index - 1;
//根據情況去移動數據
if (numMoved > 0)
//從index+1開始,所有數據向前移動一位,這樣,index位置上的數據就被覆蓋了,相當於移除了
//這裏可以看出,移除操作的效率不高
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//容器的最後一位置空,更新size
elementData[--size] = null;
}
說明:與remove(int index)相比,多了一步遍歷查詢的操作
indexOf()的分析
//根據Object找到第一個出現的位置
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;
}
//沒有找,返回-1
return -1;
}
這裏代碼與LinkedList中的類似,都是遍歷對比。效率高也僅僅是這裏鎖定了內存。
結語
瞭解了ArrayList的原理,對我們優化代碼,提升效率會有很大的幫助,這裏也給出使用建議:
如果查詢操作較多,使用ArrayList的效果更好.
與LinkedList源碼理解放在一起閱讀,效果更好
轉載請標明出處http://blog.csdn.net/qq_26411333/article/details/51583376#t10