ArrayList
ArrayList介紹
我們先看一下ArrayList的繼承關係圖:
②.ArrayList允許所有元素,包括null元素可重複.往ArrayList不斷新增元素,其容量也自動增長,可以使用ensureCapacity()方法在新增大量元素前來增加ArrayList例項的容量,這樣可以減少增量重新分配的數量.
③.ArrayList執行緒不安全,可以用 Collections.synchronizedList()使ArrayList執行緒安全,或者使用結構類似的CopyOnWriteArrayList(若讀的場景多用CopyOnWriteArrayList,寫場景多用Collections.synchronizedList,vector不建議用).
④.當某一個執行緒通過iterator遍歷ArrayList的過程中,若ArrayList中元素被其他執行緒所改變了;那麼執行緒A訪問集合時,就會丟擲ConcurrentModificationException異常,產生fail-fast事件.
⑤.ArrayList刪除和插入指定索引方法的最壞估計時間為O(n),更確切地說是O(n-index),即在索引為0操作時所需時間最長,在末尾位置操作所需時間最少.
不介紹函數語言程式設計
原始碼解析
ArrayList欄位
/**
* 序列號
*/
private static final long serialVersionUID = 8683452581122892189L;
/**
* 預設容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空陣列,定義容量為0的有參構造,返回此空陣列public ArrayList(0)
* 或者定義長度為0集合的有參構造,也會返回
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 預設的空陣列,無參構造返回此空陣列
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 儲存新增到ArrayList中的元素,不可序列化,可以看出ArrayList基於陣列實現
*/
transient Object[] elementData;
/**
* ArrayList元素數量(非容量)
*/
private int size;
/**
* 最大容量,若還是不夠用可以擴容最大Integer.MAX_VALUE
* 因為索引是int型,所以最大容量為Integer.MAX_VALUE
* 為什麼減8,不是減其他數字?
*[有點偏而且我看得也很懵。。](https://stackoverflow.com/questions/35756277/why-the-maximum-array-size-of-arraylist-is-integer-max-value-8),
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
複製程式碼
構造方法
/**
* 若知道陣列大小,可以直接用此構造
* 可能丟擲java.lang.OutOfMemoryError: Requested array size exceeds VM limit
* @param 初始容量
* @throws IllegalArgumentException 若容量為負則拋異常
*/
public ArrayList(int initialCapacity) {
// 容量大於0時
if (initialCapacity > 0) {
// 建立一個此大小的Object陣列賦給elementData
this.elementData = new Object[initialCapacity];
// 容量為0時
} else if (initialCapacity == 0) {
// 將空陣列賦給elementData
this.elementData = EMPTY_ELEMENTDATA;
// 容量小於0時丟擲IllegalArgumentException異常
} else {
throw new IllegalArgumentException("Illegal Capacity: " +
initialCapacity);
}
}
/**
* 無參構造,在新增操作時當elementData為空陣列時會初始化其容量設定成10
*/
public ArrayList() {
// 將空陣列賦給elementData
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 引數Collection的構造方法
*
* @param c 元素被放入此ArrayList中的集合c
* @throws NullPointerException 若集合c為空則拋異常
*/
public ArrayList(Collection c) {
//集合轉Object[]陣列
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toarray可能不返回Object[](見JAVA BUG編號6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 空陣列賦給elementData
this.elementData = EMPTY_ELEMENTDATA;
}
}
複製程式碼
JAVA BUG編號6260652
/** List的toArray()方法 預期返回Object[],可能向上轉型返回子型別,如下面的返回
* String[]陣列,這也就意味著我們只能往此陣列裡放入String型別元素
*/
public static void main(String[] args) {
List l = Arrays.asList(args);
System.out.println(l.toArray().getClass()); //返回String[]
}
複製程式碼
CRUD
Create
ArrayList新增(public)有如下幾個方法:
public boolean add(E e) //末尾插入某元素
public void add(int index, E element) //指定位置插入某元素
public boolean addAll(Collection<? extends E> c)//末尾插入集合(順序不變)
public boolean addAll(int index, Collection<? extends E> c)//指定位置插入集合
ArrayList是一個動態陣列,其容量會自動增長,那它如何實現的?我們來看一下add()方法,在其給當前末尾元素後一個位置新增元素時,先執行了ensureCapacityInternal方法:
public boolean add(E e) {
//確定ArrayList容量大小
ensureCapacityInternal(size + 1);
//ArrayList長度加一,在size+1位置上新增元素
elementData[size++] = e;
return true;
}
複製程式碼
在ensureCapacityInternal方法裡會先調calculateCapacity計算所需容量,若elmentData為空陣列時容量會設定成10,若不是返回操作所需容量.
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, intminCapacity){
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
複製程式碼
最後呼叫ensureExplicitCapacity方法確認是否擴容.方法裡先是將modCount加1(modCount記錄ArrayList修改次數,用來實現fail-fast機制的).再判斷所需容量是否超過當前容量,若大於則擴容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
複製程式碼
擴容方法:
private void grow(int minCapacity) {
//獲取ArrayList中elementData陣列的記憶體空間長度
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);
//Arrays.copy進行陣列拷貝(方法裡面會會呼叫System.arraycopy屬於淺拷貝)
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 檢查是否溢位,若沒有溢位,返回int最大值或定義的最大值
*/
private static int hugeCapacity(int minCapacity) {
//記憶體溢位
if (minCapacity < 0)
throw new OutOfMemoryError();
//大於定義的最大值返回int最大值,小於返回定義的最大值
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
複製程式碼
其餘新增方法類似,稍微略帶下:
/**
* 指定位置新增元素
*/
public void add(int index, E element) {
//判斷index是否越界
rangeCheckForAdd(index);
//判斷是否需要擴容
ensureCapacityInternal(size + 1);
//public static native void arraycopy(Object src, int srcPos,
// Object dest, int destPos, int length); 本地方法
// src:源陣列,srcPos:源陣列要複製的起始位置,dest:目標陣列
// destPos:目標陣列放置的起始位置,length:複製長度
// index位置及其後面的元素往後移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//指定位置賦值
elementData[index] = element;
//ArrayList長度加一
size++;
}
/**
* 末尾依次新增集合所有元素,add(Collection)與addAll(Collection)區別在於add * 是新增集合,addAll是新增集合所有元素
*/
public boolean addAll(Collection c) {
//引數集合轉為陣列
Object[] a = c.toArray();
//獲取陣列長度
int numNew = a.length;
//判斷是否擴容
ensureCapacityInternal(size + numNew); // Increments modCount
//將a的第0位開始拷貝至elementData的size位開始,拷貝長度為numNew
System.arraycopy(a, 0, elementData, size, numNew);
// size增加numNew
size += numNew;
//空集合返回false,不為空返回true
return numNew != 0;
}
/**
* 指定位置插入集合所有元素
*/
public boolean addAll(int index, Collection c) {
//判斷index是否越界
rangeCheckForAdd(index);
//引數集合轉為陣列
Object[] a = c.toArray();
//獲取陣列長度
int numNew = a.length;
//判斷是否擴容
ensureCapacityInternal(size + numNew);
int numMoved = size - index;
//若是末尾插入直接插入集合,若不是先移動原來集合中元素再插入以免被覆蓋
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
// size增加numNew
size += numNew;
//空集合返回false,不為空返回true
return numNew != 0;
}
複製程式碼
Retrieve
ArrayList查詢(public)有如下幾個方法:
public boolean contains(Object o)//查詢是否包含某元素
public int indexOf(Object o)//查詢某元素首次出現在ArrayList的索引
/**
* 內部呼叫了indexOf,若-1則表示沒有,不小於0表示有
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
/**
* for迴圈從索引為0往後開始遍歷ArrayList,返回元素首次出現位置,若不存在返回 -1
*/
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 E get(int index)//查詢此索引位置的元素
public E get(int index) {
// 檢查是否越界
rangeCheck(index);
// 返回ArrayList的elementData陣列index位置的元素
return elementData(index);
}
返回指定位置的值
E elementData(int index) {
return (E) elementData[index];
}
複製程式碼
public boolean isEmpty() //查詢ArrayList大小是否為0
/**
* ArrayList大小為0返回true,否則返回false
*/
public boolean isEmpty() {
return size == 0;
}
複製程式碼
public int lastIndexOf(Object o) //查詢某元素最後一次出現在ArrayList的索引
/**
* for迴圈從索引為size-1往前開始遍歷ArrayList,返回元素首次出現位置,若不存在返回 -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;
}
複製程式碼
public int size() //返回ArrayList的長度
/**
* 直接返回size
*/
public int size() {
return size;
}
複製程式碼
public List subList(int fromIndex, int toIndex)
/**
* 返回指定[fromIndex, toIndex)區間的集合檢視,fromIndex == toIndex時返回空集合
* 此子集合具有集合所有操作
*/
public List subList(int fromIndex, int toIndex) {
//越界檢查,非法引數檢查
subListRangeCheck(fromIndex, toIndex, size);
//返回subList有參構造
return new SubList(this, 0, fromIndex, toIndex);
}
/**
* 內部類,實現了RandomAccess提供快速訪問
*/
private class SubList extends AbstractList implements RandomAccess {
private final AbstractList parent;
private final int parentOffset;
private final int offset;
int size;
/**
* 從這裡可以看出,subList用的是父集合引用,即對subList操作的同時會影父集合
*/
SubList(AbstractList parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
}
複製程式碼
Update
ArrayList更新(public)有如下幾個方法:
public void ensureCapacity(int minCapacity)
/**
* 此方法用於若新增大量元素,可以先呼叫此方法分配多點記憶體,從而避免多次重新分配記憶體
*/
public void ensureCapacity(int minCapacity) {
// 最小擴充容量,預設是 10
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
複製程式碼
public E set(int index, E element)
/**
* 設定index位置元素的值,返回被替換元素
*/
public E set(int index, E element) {
//校驗是否越界
rangeCheck(index);
//取出舊值
E oldValue = elementData(index);
//替換元素
elementData[index] = element;
//返回被替換元素
return oldValue;
}
複製程式碼
public void trimToSize()
/**
* 將容量修改成ArrayList實際大小,用來減少記憶體空間資源浪費
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
複製程式碼
Delete
ArrayList刪除(public)有如下幾個方法:
public E remove(int index)
/**
* 刪除指定索引位置元素
*/
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;
return oldValue;
}
複製程式碼
public boolean remove(Object o) //刪除某元素,只會刪除第一次出現的
/**
* for迴圈遍歷ArrayList,若集合存在此元素呼叫fastRemove快速移除
*/
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(int index)區別,其沒有校驗是否越界,也不用返回被替換元素
*/
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;
}
複製程式碼
public boolean removeAll(Collection<?> c) //刪除ArrayList與c中的相同元素
public boolean removeAll(Collection c) {
//當c為null拋空指標異常
Objects.requireNonNull(c);
return batchRemove(c, false);
}
/**
* 批量移除
* complement為true時保留ArrayList與集合c的相同元素
* false時移除相同元素
*/
private boolean batchRemove(Collection c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
//false記錄不同值,true記錄相同值
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// 防止丟擲異常沒有遍歷完,保留未遍歷的元素
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
// 若不是全被替換,將w及其位置後面的元素置為null
if (w != size) {
for (int i = w; i < size; i++)
elementData[i] = null;
//記錄改變次數
modCount += size - w;
//設定大小
size = w;
//修改成功
modified = true;
}
}
return modified;
}
複製程式碼
public boolean retainAll(Collection<?> c) //保留ArrayList和集合c中的相同元素`
public boolean retainAll(Collection c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
複製程式碼
public void clear()
/**
* 清空所有元素
*/
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
複製程式碼
轉換陣列
/**
* 用Arrays.copyOf()轉為陣列
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
/**
* @param 陣列a
* @return 將ArrayList裡元素賦值到陣列中
* @throws NullPointerException a為null
* @throws ArrayStoreException a的執行時型別不是ArrayList中每個元素的執行時型別的超型別
*/
public T[] toArray(T[] a) {
if (a.length < size)
// 建立一個新的a的執行時型別陣列,內容不變
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
複製程式碼
序列化
我們知道ArrayList實現了Serializable,其elementData是一個快取陣列,其容量少數情況會滿,只需要序列化實際儲存的元素即可,所以用transient修飾標記不可序列化.ArrayList通過獨特方式完成序列化,在序列化時會呼叫定義的writeObject,將size和element寫入物件輸出流中.
/**
* 序列化
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
int expectedModCount = modCount;
//預設序列化方法,將當前類的非靜態和非transient欄位寫入流中
s.defaultWriteObject();
// 寫入大小
s.writeInt(size);
// for迴圈寫入ArrayList中所有元素
for (int i=0; i < size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
/**
* 反序列化
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// 預設反序列化方法
s.defaultReadObject();
// 讀取長度
s.readInt(); // ignored
if (size > 0) {
// 像clone()方法 ,根據大小而不是容量分配陣列
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
//讀取所有元素
for (int i=0; i < size; i++) {
a[i] = s.readObject();
}
}
}
複製程式碼
遍歷
在簡述中提到了ArrayList實現了RandomAccess介面,其有一段英文註釋
As a rule of thumb, a List implementation should
implement this interface if,
for typical instances of the class, this loop:
for (int i=0, n=list.size(); i < n; i++)
list.get(i);
runs faster than this loop:
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();
複製程式碼
for迴圈遍歷效率高過foreach,在Collections工具類中個方法:
private static final int FILL_THRESHOLD = 25;
public static void fill(List list, T obj) {
int size = list.size();
if (size < FILL_THRESHOLD || list instanceof RandomAccess) {
for (int i=0; i itr = list.listIterator();
for (int i=0; i < size; i++) {
itr.next();
itr.set(obj);
}
}
}
複製程式碼
我們可以從這個方法可以看出若是實現了RandomAccess用了for迴圈遍歷.
ArrayList的iterator與listIterator區別
iterator與listIterator都是迭代器,不同的是listIterator可以指定位置開始,擁有hasPrevious()和previous()方法能逆向遍歷,可以對集合本身進行新增,修改不過必須要next()過且不能連續操作,還可以返回當前遍歷位置nextIndex(),previousIndex()當前遍歷位置前一個.
手撕簡單實現
public class ArrayList {
private Object[] elementData;
private int size;
public ArrayList(int capacity){
this.elementData = new Object[capacity];
}
public ArrayList(){
this.elementData = new Object[10];
}
public void add(Object obj){
isExpanse(size + 1);
elementData[size++] = obj;
}
private void isExpanse(int minCapacity){
int oldCapacity = elementData.length;
if(minCapacity > oldCapacity){
int newCapacity = oldCapacity + oldCapacity >> 1;
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
public Object get(int index){
checkLength(index);
return elementData[index];
}
public void set(int index, Object obj){
checkLength(index);
elementData[index] = obj;
}
private void checkLength(int index){
if(index < 0 || index >= size) {
throw new IndexOutOfBoundsException("越界");
}
}
public void remove(int index){
checkLength(index);
int moveNum = size - index - 1;
if(moveNum > 0 ){
System.arraycopy(elementData, index + 1, elementData, index, moveNum);
}
elementData[size--] = null;
}
}
複製程式碼