List源碼解析之ArrayList源碼分析
ArrayList簡介
ArrayList是基於數組實現的, 是一個動態擴展的數組,容量可自動增長。
ArrayList是非線程安全的,只能在單線程環境下使用,多線程環境考慮使用Collections.synchronizedList(List
ArrayList實現了Serializable接口,因此它支持序列化,能夠通過序列化傳輸,實現了RandomAccess接口,支持快速隨機訪問,實際上就是通過下標序號進行快速訪問,實現了Cloneable接口,能被克隆。
屬性和構造函數
private static final int DEFAULT_CAPACITY = 10; // 默認初始值
transient Object[] elementData; // 存放數據的數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 存放的數組默認容量10
protected transient int modCount = 0; // List被修改的次數
// 構造函數
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() { // 默認容量10的數組
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 構造一個包含指定collection 的元素的列表,這些元素按照
* 該collection 的叠代器返回它們的順序排列的
*/
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;
}
}
存儲
set(int index, E element)
// 用指定的元素替代此列表中指定位置上的元素,並返回以前位於該位置上的元素
public E set(int index, E element) {
rangeCheck(index); // 越界檢測
E oldValue = elementData(index);
elementData[index] = element; // 賦值到指定位置,復制的僅僅是引用
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
add(E e)
// 添加一個元素到此列表的尾部 時間復雜度O(1),非常高效
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 容量 + 1
elementData[size++] = e;
return true;
}
// 如有必要,增加此 ArrayList 實例的容量,以確保它至少能夠容納最小容量參數所指定的元素數
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 增加容量,確保可容納元素
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); // 擴容並復制
// 由於Java GC自動管理了內存,這裏也就不需要考慮源數組釋放的問題。
// 關於Java GC這裏需要特別說明一下,有了垃圾收集器並不意味著一定不會有內存泄漏。對象能否被GC的依據是是否還有引用指向它,上面代碼中如果不手動賦null值,除非對應的位置被其他元素覆蓋,否則原來的對象就一直不會被回收。
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
空間問題解決後,插入就變得容易了
add(int index, E element)
// 將指定的元素添加到此列表中的指定位置
// 如果當前位置有元素,則向右移動當前位於該位置的元素以及所有後續元素(將其索引加1)
public void add(int index, E element) {
rangeCheckForAdd(index); // 數組越界檢測
// 如果數組長度不足,將進行擴容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 將 elementData 中從Index 位置開始、長度為size-index 的元素,
// 拷貝到從下標為index+1 位置開始的新的elementData 數組中。
// 即將當前位於該位置的元素以及所有後續元素右移一個位置。
System.arraycopy(elementData, index, elementData, index + 1,
size - index); // 低效
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
addAll(Collection<? extends E> c)
// 按照指定collection 的叠代器所返回的元素順序,將該collection 中的所有元素添加到此列表的尾部
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;
}
addAll(int index, Collection<? extends E> c)
// 從指定的位置開始,將指定collection 中的所有元素插入到此列表中
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
讀取
get(int index)
// 返回此列表中指定位置上的元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index]; //註意類型轉換
}
刪除
根據下標和指定對象刪除,刪除時,被移除元素以後的所有元素向左移動一個位置。
remove(int index)
// 刪除指定位置的元素,並返回刪除元素
public E remove(int index) {
rangeCheck(index)![ArrayList_add.png](http://upload-images.jianshu.io/upload_images/626005-4973ec5f1213fd0f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
;
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,清除該位置的引用,讓GC起作用
return oldValue;
}
remove(Object o)
// 移除此列表中首次出現的指定元素(如果存在)。這是應為ArrayList 中允許存放重復的元素
public boolean remove(Object o) {
// 由於ArrayList 中允許存放null,因此下面通過兩種情況來分別處理
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 類似remove(int index),移除列表中指定位置上的元素
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++;
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
}
容量調整
如果添加元素前已經預測到了容量不足,可手動增加ArrayList實例的容量,以減少遞增式再分配的數量。
ensureCapacity(int minCapacity)
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
// 具體查看ensureExplicitCapacity
在ensureExplicitCapacity中,可以看出,每次擴容時,會將老數組中的元素重新拷貝一份到新的數組中(System.arraycopy()方法),每次數組容量的增長大約是其原容量的1.5 倍。這種操作的代價是很高的,因此要盡量避免數組容量擴容,使用時盡可能的指定容量,或者根據需求,通過調用ensureCapacity 方法來手動增加ArrayList 實例的容量。
trimToSize()
該方法是將底層數組的容量調整為當前列表保存的實際元素的大小。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
遍歷
通過叠代器遍歷
Integer value = null; Iterator iter = list.iterator(); while (iter.hasNext()) { value = (Integer)iter.next(); }
隨機訪問,通過索引值去遍歷(推薦)
Integer value = null; int size = list.size(); for (int i=0; i<size; i++) { value = (Integer)list.get(i); }
ForEach循環遍歷
Integer value = null; for (Integer integ:list) { value = integ; }
經測試,耗時:ForEach循環 > 隨機 > 叠代器,優先使用叠代器方式 。
ForEach的本質也是叠代器模式,可以反編譯查看,而且還多了一步賦值操作,增加了開銷。
總結
size(), isEmpty(), get(), set()方法均能在常數時間內完成,add()方法的時間開銷跟插入位置有關,addAll()方法的時間開銷跟添加元素的個數成正比。其余方法大都是線性時間。
排列有序(索引從0開始),可插入空值,可重復
底層數組實現
讀取快,增刪慢(需要移動元素,插入刪除效率低)
非同步,線程不安全
初始容量默認為10,當容量不夠時,ArrayList是當前容量 * 1.5 + 1
擴容時,需要調用System.arraycopy,copy本來就是一個耗時的操作,所以盡量初始化容量。
即使理論上效率還可以
(System.arraycopy()方法是一個native的,最終調用了C語言的memmove()函數,比一般的復制方法的實現效率要高很多。)創建時,初始化最小容量
?
參考
- CarpenterLee
- 莫等閑
- 蘭亭風雨
======華麗麗的分隔線======
作者:jimzhang
出處:http://www.jianshu.com/p/2f282e5ce305
版權所有,歡迎保留原文鏈接進行轉載:)
List源碼解析之ArrayList源碼分析