面試題之---ArrayList實現原理
單列集合圖
1. ArrayList是一個動態陣列,實現了List<E>, RandomAccess, Cloneable, java.io.Serializable,並允許包括null在內的所有元素。
1.1,實現了RandomAccess介面標識著其支援隨機快速訪問,實際上,我們檢視RandomAccess原始碼可以看到,其實裡面什麼都沒有定義.因為ArrayList底層是陣列,那麼隨機快速訪問是理所當然的,訪問速度O(1).
1.2,現了Cloneable介面,標識著可以它可以被複制.注意,ArrayList裡面的clone()複製其實是淺複製
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** * 預設長度是10 */ private static final int DEFAULT_CAPACITY = 10;
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// 預設長度是10
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); }}
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倍, oldCapacity>>1表示往右移一個單位,就是除以2的1次方 int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果擴為1.5倍還不滿足需求,直接擴為你需要的大小
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//將原來的長度,拷貝變成擴充套件後的大小
elementData = Arrays.copyOf(elementData, newCapacity);
}
2. 底層使用陣列實現,預設初始容量為10.當超出後,會自動擴容為原來的1.5倍,即自動擴容機制。
陣列的擴容是新建一個大容量(原始陣列大小+擴充容量)的陣列,然後將原始陣列資料拷貝到新陣列,然後將新陣列作為擴容之後的陣列。陣列擴容的操作代價很高,我們應該儘量減少這種操作。
3. 該集合是可變長度陣列,陣列擴容時,會將老陣列中的元素重新拷貝一份到新的陣列中,每次陣列容量增長大約是其容量的1.5倍,如果擴容一半不夠,就將目標size作為擴容後的容量.這種操作的代價很高。採用的是 Arrays.copyOf淺複製,
3.1這裡簡單說一下什麼是淺複製
淺複製:只複製一個物件,但新物件和老物件同是一個地址值,
深複製:複製一個物件,新老物件的地址值也變了.
詳情請看(要了解什麼是淺複製):點選開啟連結https://blog.csdn.net/qq_38859786/article/details/80318977
4. 採用了Fail-Fast機制,面對併發的修改時,迭代器很快就會完全失敗,報異常concurrentModificationException(併發修改一次),而不是冒著在將來某個不確定時間發生任意不確定行為的風險
5. remove方法會讓下標到陣列末尾的元素向前移動一個單位,並把最後一位的值置空,方便GC
6. 陣列擴容代價是很高的,因此在實際使用時,我們應該儘量避免陣列容量的擴張。當我們可預知要儲存的元素的多少時,要在構造ArrayList例項時,就指定其容量,以避免陣列擴容的發生。或者根據實際需求,通過呼叫ensureCapacity方法來手動增加ArrayList例項的容量。
7. ArrayList不是執行緒安全的,只能用在單執行緒環境下,多執行緒環境下可以考慮用Collections.synchronizedList(List l)函式返回一個執行緒安全的ArrayList類,也可以使用concurrent併發包下的CopyOnWriteArrayList類。
eventbus的訂閱方法subscribe()裡面,就採用了執行緒較為安全的CopyOnWriteArrayList集合
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
8,add(E e)方法作用: 新增指定元素到末尾
如果要增加的資料量很大,應該使用ensureCapacity()方法,該方法的作用是預先設定Arraylist的大小,這樣可以大大提高初始化速度.
Object obj = new Object();
ArrayList list0 = new ArrayList();
long startTime0 = System.currentTimeMillis();
for(int i=0;i<=N;i++){
list0.add(obj);
}
long endTime0 = System.currentTimeMillis();
Log.e("date", "111沒有呼叫ensureCapacity()方法所用時間:" + (endTime0 - startTime0) + "ms");
ArrayList list1 = new ArrayList();
long startTime1 = System.currentTimeMillis();
list1.ensureCapacity(N);//預先設定list的大小
for(int i=0;i<=N;i++){
list1.add(obj);
}
long endTime1 = System.currentTimeMillis();
Log.e("date", "222呼叫ensureCapacity()方法所用時間:" + (endTime1 - startTime1) + "ms");
9,如果是新增到陣列的指定位置,那麼可能會挪動大量的陣列元素,並且可能會觸發擴容機制;如果是新增到末尾的話,那麼只可能觸發擴容機制.
10,如果是刪除陣列指定位置的元素,那麼可能會挪動大量的陣列元素;如果是刪除末尾元素的話,那麼代價是最小的. ArrayList裡面的刪除元素,其實是將該元素置為null.
11,Collection是最頂層的集合,Collection.toArray()在Collection各個子類的原始碼中使用頻繁
12,Arrays.copyOf(U[] original, int newLength, Class<? extends T[]> newType),就是根據class的型別來決定是new還是反射去構造一個泛型陣列,同時利用native函式,批量賦值元素至新陣列中.
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
//根據class的型別來決定是new還是反射去構造一個泛型陣列
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
//利用native函式,批量賦值元素至新陣列中
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
13, System.arraycopy()複製陣列,也是一個高頻使用的函式.
@FastNative
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
借鑑:
集合各實現類的底層實現原理
CopyOnWriteArrayList詳解
Java中ArrayList和LinkedList區別