ArrayList集合特點及原始碼分析
ArrayList是List介面的實現類
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
繼承了AbstractList
類,實現了Cloneable克隆介面、Serializable序列化介面、RandomAccess隨機訪問介面、List
特點:底層使用陣列實現的 查詢效率高,增刪慢,執行緒不安全、允許為空、可重複
public static void main(String[] args) {
List<String> list=new ArrayList<String>();
boolean bool=list.add("ylc");//Collection介面新增元素
list.add(1,"ww");//根據索引新增元素
String el=list.get(1);//根據索引獲取元素
list.size();//獲取元素個數
list.remove(2);//根據元素位置刪除
list.remove("ylc");//刪除指定元素
list.set(1,"yy");//替換元素
list.clear();//清空集合
list.isEmpty();//判斷元素是否為空
list.contains("ylc");//判斷集合是否包含某個元素
list.indexOf("ylc");//查詢所訴中所在的位置
list.lastIndexOf("ylc");//元素最後出現的索引位置
Object[] objects=list.toArray();//把集合轉化為object陣列
for (int i = 0; i < objects.length; i++) {
String str=(String) objects[i];
System.out.println(str);
}
String[] strings=list.toArray(new String[list.size()]);//把集合轉化為指定型別陣列
List<String> list2=new ArrayList<String>();
list2.add("s");
list.addAll(list2);//集合合併操作
list.retainAll(list2);//集合交集操作 list儲存交集內容
list.removeAll(list2);//刪除list中含有list2集合的元素
}
ArrayList原始碼分析
成員變數
private static final int DEFAULT_CAPACITY = 10;//陣列預設長度
private static final Object[] EMPTY_ELEMENTDATA = {};//給定一個空陣列
transient Object[] elementData;//儲存ArrayList元素的臨時陣列 不會被存到磁碟
private int size;//記錄陣列中元素的個數
protected transient int modCount = 0; // 集合陣列修改次數的標識(fail-fast機制)
transient關鍵字對於不想進⾏序列化的欄位,使⽤ transient 關鍵字修飾
JDK7中,只要建立ArrayList陣列,就會預設建立一個長度為10的空陣列。
JDK8中,做了一個延遲載入,在建立ArrayList陣列時,建立一個長度為0的空陣列,只有在用到這陣列才會對長度進行改變,做了一個延遲載入
建構函式
裡面包含三種建構函式
1.無參建構函式
初始化陣列時預設賦值一個空陣列
//無參建構函式
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//預設為0
}
2.int型別的建構函式
如果傳入數值大於0就建立指定容量大小的陣列,數值為0為空物件陣列,否則丟擲異常
//帶容量大小的建構函式
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);
}
}
3.集合型別建構函式
把傳入的集合變成陣列,賦值給elementData
集合不為空,傳入陣列型別轉為object的話賦值給elementData
否則就直接複製到elementData中
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();//變成陣列
if ((size = a.length) != 0) {//集合不為空的話
if (c.getClass() == ArrayList.class) {//判斷與ArrayList型別是否一致
elementData = a;//賦值
} else {
elementData = Arrays.copyOf(a, size, Object[].class);//否則直接複製到陣列中
}
} else {
elementData = EMPTY_ELEMENTDATA;//設定為空陣列
}
}
增加方法
add(E e)方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); //當size=0時
elementData[size++] = e;//Size還是為0,給為0的size賦值
return true;
}
//確保集合陣列內部容量
private void ensureCapacityInternal(int minCapacity) {//1
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//返回10
}
//確保顯式容量
private void ensureExplicitCapacity(int minCapacity) {//minCapacity=10
modCount++;
if (minCapacity - elementData.length > 0)// 10-0>0 判斷擴容關鍵程式碼 當第11個陣列加入時執行grow程式碼
grow(minCapacity);//10
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//計算陣列容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//0=0
return Math.max(DEFAULT_CAPACITY, minCapacity);// DEFAULT_CAPACITY=10,minCapacity=1 返回最大值作為陣列容量
}
return minCapacity;
}
//擴容方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;//0
int newCapacity = oldCapacity + (oldCapacity >> 1); //陣列長度加上位移運算(陣列長度的一半)每次擴容增加1.5倍 0=0+0
if (newCapacity - minCapacity < 0)//0-10<0
newCapacity = minCapacity; //newCapacity=0
if (newCapacity - MAX_ARRAY_SIZE > 0)//10-MAX_ARRAY_SIZE<0
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);//複製操作 把一個長度為10複製到空的陣列中,生成了一個新的長度為10的空陣列
}
Size預設是0,add方法給Size加上1,並傳入ensureCapacityInternal
方法,其中ensureCapacityInternal
方法中又呼叫了ensureExplicitCapacity
方法,並傳入了兩個引數,第一個是elementData
代表一個為空的陣列,第二個是陣列個數。在calculateCapacity
方法中,有個if判斷如果陣列大小等於預設大小,就返回其中最大的數值,預設陣列大小為10的話,DEFAULT_CAPACITY
也等於10,所以返回的是10。把10傳入ensureExplicitCapacity
方法中,再把10 傳入grow
方法中,生成了一個新的長度為10的空陣列。ensureCapacityInternal
方法執行完畢。add
方法Size依然為0,給為下標為0索引賦值,新增一個成功。
擴容模擬
當新增第11個元素時,add方法Size=10+1=11,傳入ensureCapacityInternal方法,進入calculateCapacity方法,這時elementData=10,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA=0,不滿足直接返回minCapacity=11,值11進入ensureExplicitCapacity方法內部,滿足11-10>0進grow方法,grow方法賦值oldCapacity=10,newCapacity=10+10的位移一位操作=10+5=15,最後使用copyOf方法,把原來10大小的陣列擴容到15。
add(int index, E element)方法
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); //集合增加容量
//elementData:原陣列 index:從原陣列這個索引開始複製 elementData:目標陣列 index + 1:目標陣列的第一個元素 size - index:複製size-index個元素
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));
}
這樣每增加一個元素就要把當前索引複製到該元素的後面,開銷很大
刪除方法
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; //集合數量減1
return oldValue;
}
E elementData(int index) {
return (E) elementData[index];//返回當前索引的元素
}
進行刪除前檢查索引是否合理,然後記錄集合被修改的次數,根據索引獲取到需要刪除的這個元素,然後該索引後面的元素複製到當前索引,進行覆蓋,最後集合元素減去。
remove(Object o)方法
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; // 集合數量減1
}
該方法利用集合遍歷找到該元素,根據索引再進行刪除,fastRemove
方法同remove(int index)方法類似。
removeAll(Collection<?> c)方法
刪除c集合中存在的元素
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);//判斷集合是否為空
return batchRemove(c, false);
}
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
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++)
if (c.contains(elementData[r]) == complement)//complement=flase 如果elementData不包含c裡面的某個元素
elementData[w++] = elementData[r];//不包含的記錄下來! 賦值到elementData,滿足就從索引0開始覆蓋掉原來的陣列
} finally {
//存在併發修改上面發生異常沒遍歷完elementData,這裡就會不相等 如果不存在這裡就會直接跳過
if (r != size) {
//elementData:原陣列 r:從原陣列這個索引開始複製 elementData:目標陣列 w:目標陣列的第一個元素 size - r:複製元素數量
System.arraycopy(elementData, r,elementData, w,size - r);//把沒有遍歷到的元素賦值到已更新元素的後面
w += size - r;//現有的資料
}
if (w != size) {//w容量是不包含的 除非集合裡刪除一個元素都不包含這裡才會跳過
for (int i = w; i < size; i++)
elementData[i] = null;//把不包含的後面多餘的陣列置為空
modCount += size - w;//增加修改次數
size = w;//當前資料長度
modified = true;//修改成功
}
}
return modified;
}
場景模擬代入數值:在一個10長度的陣列a中刪除一個3長度的陣列b,刪除到一半的時候,由於多執行緒導致陣列賦值失敗進入finally程式碼塊,此時w=3,r=6,size=10,w存入的是不包含在b陣列中的元素現有3個,r是遍歷到中斷的索引,進入(r != size)判斷不相等,arraycopy方法把後面沒有遍歷到的重新加進來陣列,w+=size - r=7;這樣w中儲存的就是c集合中不包含的全部元素,再遍歷整個Size集合,把不包含元素後面的容量置為空。
其他方法
indexOf(Object o)方法
獲取索引
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;//未匹配
}
lastIndexOf(Object o)
倒序獲取索引
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;//未匹配
}
set(int index, E element)方法
public E set(int index, E element) {
rangeCheck(index);//檢查索引
E oldValue = elementData(index);//根據索引獲取元素
elementData[index] = element;//把元素賦值給當前索引
return oldValue;
}
get(int index)方法
public E get(int index) {
rangeCheck(index);//檢查索引
return elementData(index);//返回根據索引獲取當前元素
}
clear方法
public void clear() {
modCount++;//修改次數+1
for (int i = 0; i < size; i++)
elementData[i] = null;//遍歷賦值為null
size = 0;//陣列中元素的個數置為0
}
總結
- 是一個動態陣列,其容量能自動增長,但每次增刪都要copy,效能不高,訪問的時候通過索引是最快的
- ArrayList執行緒不安全,只能用在單執行緒環境下,多執行緒環境下可以考慮用Collections.synchronizedList(List l)函式返回一個執行緒安全的ArrayList類,也可以使用concurrent併發包下的CopyOnWriteArrayList類