Java源碼篇之容器類——ArrayList
1、前言
對於平常開發的時候遇到的ArrayList,在此做一個簡單的源碼閱讀記錄,JDK1.8版本。
2、ArrayList的類關係
首先看下ArrayList的類關係圖,可以看到實現了
-
Serializable接口,支持序列化與反序列化;
-
Cloneable接口,可以被克隆;
-
RandomAccess接口,支持隨機訪問,另外fori循環會比迭代器循環效率高,代碼如下:
for (int i=0, n=list.size(); i < n; i++) list.get(i);
比這個循環運行得更快:
for (Iterator i=list.iterator(); i.hasNext(); ) i.next();
3、ArrayList的源碼
一、類的屬性
/**
* 默認初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* (用於空實例的)共享空數組實例
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* (用於默認大小的空實例的)共享空數組實例,我們將其與空的元素數據區分開來,
* 以瞭解何時添加第一個元素.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存儲ArrayList元素的數組緩衝區。ArrayList的容量是此數組緩衝區的長度。任何
* elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA elementData
* 將在添加第一個元素時擴展到默認容量(10)。
*/
transient Object[] elementData; // transient關鍵字標記的成員變量不參與序列化過程
/**
* ArrayList的大小(包含的元素數)
*/
private int size;
/**
* 可分配的數組的最大大小,可能會導致OutOfMemory錯誤:Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
二、add()方法
// 默認往數組末尾添加元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 大小加1
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) { // minCapacity = size + 1
// elementData 是成員變量
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {/ 如果
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 數組爲空時,返回較大的數
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity; // 數組非空時,返回 minCapacity = size + 1
}
private void ensureExplicitCapacity(int minCapacity) { // minCapacity = size + 1
modCount++; // 計數
// 數組元素個數加1之後如果大於當前數組長度,則進行擴容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) { // minCapacity = size + 1
int oldCapacity = elementData.length; // 舊數組的長度
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新數組的長度 = 1.5倍舊數組長度
// 新數組長度小於數組元素個數加1
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 新數組長度大於數組最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 創建一個新的數組,並把舊數組元素複製過去,newCapacity爲新數組大小
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
// 新數組長度大於數組最大值,並且minCapacity<0纔會拋出oom錯誤
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
// 指定位置添加元素
public void add(int index, E element) {
// 下標合法性校驗
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
// 生成一個index位置爲null的新數組
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));
}
三、get()方法
public E get(int index) {
// 檢查合法性
rangeCheck(index);
// 返回數組元素
return elementData(index);
}
private void rangeCheck(int index) {
// 下標位置大於等於數組長度的時候(數組下標從0開始),拋出下標越界異常
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E elementData(int index) {
return (E) elementData[index];
}
四、remove()方法
// 刪除指定下標的元素
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;
}
// 刪除指定的元素,第一個出現的
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;
}
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
}
五、set()方法
// 將index位置的元素置換成element
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
4、總結
- 通過閱讀源碼可以看出,ArrayList的這些方法都沒有加鎖,所以在多線程環境下是不安全的;
- add()和remove()方法都需要創建新的數組,是比較消耗性能的;