1.簡介:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- 繼承了AbstractList,提供相關的修改、刪除、遍歷等功能
- 實現了RandomAccess接口,提供了隨機訪問訪問功能,即可以通過元素序號訪問元素
- 實習Cloneable接口,可以克隆
- 實現了Serializable接口,實現了序列化的功能
其他:ArrayList不是線程安全的,多線程可以考慮使用Vector或者CopyOnWriteArrayList
特點:
1. 不是線程安全的,不是線程同步的。
2.允許null在內的所有元素。
3.ArrayList中存放順序和添加順序是一致的。並且可重複元素。
4. ArrayList實現了可變大小的數組。
2.ArrayList屬性:
private static final long serialVersionUID = 8683452581122892189L;
//數組的初始容量
private static final int DEFAULT_CAPACITY = 10;
//當用戶指定容量爲0時
//elementData=EMPTY_ELEMENTDATA
private static final Object[] EMPTY_ELEMENTDATA = {};
//當用戶未指定容量時
//elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA
//當用戶第一次添加元素時,會擴容爲10
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//transient修飾代表不會被序列化
//ArrayList用該數組保存數據
transient Object[] elementData; // non-private to simplify nested class access
private int size;
3.構造函數
(1) 由用戶指定容量的構造函數
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);
}
}
當 initialCapacity=0時,elementData = EMPTY_ELEMENTDATA;
(2)無參構造函數
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
3.重要方法:
1.add()
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
線程安全:我們注意到elementData[size++] = e;這條語句其實就是添加元素的關鍵,其實這條語句可以拆成兩條語句來執行也就是elementData[size] = e 和 size++,試想一下,如果在多線程訪問同一個list的時候,線程A執行到elementData[size] = e時,時間片到了讓出CPU,此時線程B執行,當線程B執行到elementData[size] = e此時size還沒有加一,(假設此時size=10)也就是說線程A在list[10]位置上添加的元素被線程B添加的元素覆蓋了,然後A,B兩個線程都執行size++;這樣造成的影響就是size變成12了,但是在第11個位置上卻沒有元素也就是說list[11]=null
add方法首先調用ensureCapacityInternal
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
可以看出,如果ArrayList是通過無參構造方法初始且第一次add,此時minCapacity=1,然後minCapacity變爲10,然後調用
ensureExplicitCapacity(10)
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
如果minCapacity>當前數組的長度才進行擴容
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);
}
Arrays.copyof調用的是本地方法 System.arraycopy() ;
Arrays.copyOf()方法返回的數組是新的數組對象,原數組對象仍是原數組對象,不變,該拷貝不會影響原來的數組
System.arraycopy() 源碼如下:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
參數說明:
src:源對象
srcPos:源數組中的起始位置
dest:目標數組對象
destPos:目標數據中的起始位置
length:要拷貝的數組元素的數量
grow分析:先把新容量變爲舊容量1.5倍,如果新容量還是小於指定容量,則新容量=指定容量,如果新容量大於最大容量,則看minCapacity與MAX_ARRAY_SIZE的大小,如果minCapacity>MAX_ARRAY_SIZE則新容量爲Integer.MAX_VALUE否則新容量爲MAX_ARRAY_SIZE
注意:如果無參構造初始的ArrayList,則在第一次添加元素時就進行擴容,第一次擴容至10,直到容量大於10才進行1.5倍擴容,如果不是無參構造初始的ArrayList,
總結:add方法: add-> ensureCapacityInternal(是否時無參構造初始的ArrayList)->ensureExplicitCapacity(所需容量是否大於當前容量)->grow(擴容)
2.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;
}
remove 中主要是將之後的元素都向前一位移動,然後將最後一位的值設置爲空。最後,返回已經刪除的值。
3.indexof方法
.indexOf方法--查找下標
/**
* 查找下標, 如果爲null,直接和null比較,返回下標
*/
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;
}
/**
* 查找最後出現的下標,從大往下循環查找
*/
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
4.總結:
與LinkedList區別
1. ArrayList的實現是基於數組,LinkedList的實現是基於雙向鏈表。
2. 對於隨機訪問,ArrayList優於LinkedList,ArrayList可以根據下標以O(1)時間複雜度對元素進行隨機訪問。而LinkedList的每一個元素都依靠地址指針和它後一個元素連接在一起,在這種情況下,查找某個元素的時間複雜度是O(n)
3. 對於插入和刪除操作,LinkedList優於ArrayList,因爲當元素被添加到LinkedList任意位置的時候,不需要像ArrayList那樣重新計算大小或者是更新索引。
4. LinkedList比ArrayList更佔內存,因爲LinkedList的節點除了存儲數據,還存儲了兩個引用,一個指向前一個元素,一個指向後一個元素。