Java1.8-ArrayList原始碼解析
概述
ArrayList可以理解為是一個可以動態擴容的陣列,因為本身就是使用陣列來實現的。
屬性
/**
* 預設容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 初始空陣列
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 預設初始容量下的空陣列,這樣我們知道在新增第一個元素的時候,應該擴容多少
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* list實際儲存資料的陣列
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* list的容量大小
*/
private int size;
方法
ArrayList中的方法挺多,我們撿幾個比較重要的使用較多的來學習以下。
add方法
add方法實現大致流程:
- 判斷陣列容量是否為空,如果是,比較預設容量與實際容量大小,取最大值;
- 判斷實際所需容量如果大於陣列容量,擴容;
- 儲存資料,size加1;
原始碼:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// 如果是空陣列,取預設容量與所需容量的最大值為實際所需容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果實際所需容量大於陣列容量,擴容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 容量擴容為原來的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果擴容後的容量還是小於實際所需容量,則將擴容後的容量設定為實際所需容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果擴容後的容量超過了系統預設的最大值:Integer.MAX_VALUE - 8,檢測是否溢位
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 使用Arrays的copyof方法將原陣列資料拷貝到新的陣列,並將新陣列賦值給變數elementData
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
// 如果溢位,提示異常,如果沒有溢位,實際所需容量是否超過系統預設的最大值,如果超過,返回Integer的最大值,如果沒有超過,返回系統預設的最大值
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
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);
// 將陣列最後一位置空,供GC呼叫
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
其實remove方法本身沒什麼好說的,主要說以下removeAll。removeAll是刪除與另一個集合的交集。
removeAll方法的實現在於,先遍歷elementData,將elementData與另一個集合c沒有交集的資料,放置在elementData的下標的0到w段,然後再清除掉下標w到size-1之間的元素就行了(即設定為null)。
public boolean removeAll(Collection<?> c) {
// 引數非空校驗
Objects.requireNonNull(c);
return batchRemove(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++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// 如果c.contains提示異常
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
// 如果有資料被刪除
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
TrimToSize方法
- 我們對陣列擴容之後,有時候陣列的容量會大於實際所需要的容量,這時候如果我們想將陣列容量調整為實際所需要的容量,可以呼叫該方法。
- 比如記憶體緊張,或者我們可以確定不會再有元素新增進來時,也可以呼叫該方法來節省空間。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
ensureCapacity方法
- 從add()與addAll()方法中可以看出,每當向陣列中新增元素時,都要去檢查新增元素後的個數是否會超出當前陣列的長度,如果超出,陣列將會進行擴容,以滿足新增資料的需求。
- 在JDK8中,JDK提供了一個public的ensureCapacity方法讓我們可以手動設定ArrayList的容量,以減少上面這種遞增時呼叫再重新分配的數量;
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);
}
}
其他
由於ArrayList實現了RandomAccess, Cloneable, java.io.Serializable,所以支援隨機讀取,複製,序列化操作,對應一些方法:
/**
* 按下標讀取
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
/**
* 複製
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
/**
* 序列化
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
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;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
問題
- ArrayList執行緒不安全體現在什麼地方?
ArrayList在新增元素的時候,可能分為兩步:擴容,在該位置設定元素值。如果兩個執行緒同時到這一步,各自擴容之後,各自在該位置設定元素值,這樣同一個位置就被set了兩次,導致資料的汙染或者丟失;
- 如何是一個ArrayList執行緒安全?
可以藉助Collections,List list = Collections.synchronizedList(new ArrayList());
- JDK 5在java.util.concurrent裡引入了ConcurrentHashMap,在需要支援高併發的場景,我們可以使用它代替HashMap。但是為什麼沒有ArrayList的併發實現呢?難道在多執行緒場景下我們只有Vector這一種執行緒安全的陣列實現可以選擇麼?為什麼在java.util.concurrent 沒有一個類可以代替Vector呢?
我認為在java.util.concurrent包中沒有加入併發的ArrayList實現的主要原因是:很難去開發一個通用並且沒有併發瓶頸的執行緒安全的List。
像ConcurrentHashMap這樣的類的真正價值(The real point / value of classes)並不是它們保證了執行緒安全。而在於它們在保證執行緒安全的同時不存在併發瓶頸。舉個例子,ConcurrentHashMap採用了鎖分段技術和弱一致性的Map迭代器去規避併發瓶頸。
所以問題在於,像“Array List”這樣的資料結構,你不知道如何去規避併發的瓶頸。拿contains() 這樣一個操作來說,當你進行搜尋的時候如何避免鎖住整個list?
另一方面,Queue 和Deque (基於Linked List)有併發的實現是因為他們的介面相比List的介面有更多的限制,這些限制使得實現併發成為可能。
CopyOnWriteArrayList是一個有趣的例子,它規避了只讀操作(如get/contains)併發的瓶頸,但是它為了做到這點,在修改操作中做了很多工作和修改可見性規則。 此外,修改操作還會鎖住整個List,因此這也是一個併發瓶頸。所以從理論上來說,CopyOnWriteArrayList並不算是一個通用的併發List。
上面第三個問題轉載自:
為什麼java.util.concurrent 包裡沒有併發的ArrayList實現?
總結
ArrayList的優點是隨機讀取,缺點是插入資料時需要移動許多資料,而與之相對應的是LinkedList。我們將再下篇文章分析以一下。
相關文章
- 【原始碼解析】- ArrayList原始碼解析,絕對詳細原始碼
- Spark原始碼-SparkContext原始碼解析Spark原始碼Context
- CountDownLatch原始碼解析CountDownLatch原始碼
- LeakCanary原始碼解析原始碼
- vuex原始碼解析Vue原始碼
- ArrayBlockQueue原始碼解析BloC原始碼
- AsyncTask原始碼解析原始碼
- CopyOnWriteArrayList原始碼解析原始碼
- Express原始碼解析Express原始碼
- Observer原始碼解析Server原始碼
- SparseArray 原始碼解析原始碼
- RecyclerView原始碼解析View原始碼
- Promise 原始碼解析Promise原始碼
- Koa原始碼解析原始碼
- RateLimiter原始碼解析MIT原始碼
- redux原始碼解析Redux原始碼
- SDWebImage原始碼解析Web原始碼
- CyclicBarrier原始碼解析原始碼
- Semaphore原始碼解析原始碼
- Exchanger原始碼解析原始碼
- AbstractQueuedSynchronizer原始碼解析原始碼
- OKio原始碼解析原始碼
- Koa 原始碼解析原始碼
- RxPermission原始碼解析原始碼
- MyBatis原始碼解析MyBatis原始碼
- ArrayList原始碼解析原始碼
- Aspects 原始碼解析原始碼
- LeakCanary 原始碼解析原始碼
- Vue原始碼解析Vue原始碼
- React原始碼解析React原始碼
- ButterKnife原始碼解析原始碼
- HashSet原始碼解析原始碼
- Retrofit 原始碼解析原始碼
- Javapoet原始碼解析Java原始碼
- Vuex 原始碼解析Vue原始碼
- OKHttp原始碼解析HTTP原始碼
- Retrofit原始碼解析原始碼
- Handler原始碼解析原始碼