前言
分析ArrayList 的源碼爲JDK8版本。
源碼分析
我們先看看一個案例:
public class test2 { public static void main(String[] args) { int index = 10000000; ArrayList arrayList = new ArrayList(); LinkedList linkedList = new LinkedList(); long time0 = System.currentTimeMillis(); for (int i = 0; i < index ; i++) { arrayList.add(i); } long time1 = System.currentTimeMillis(); System.out.println(time1 - time0); long time2 = System.currentTimeMillis(); for (int i = 0; i < index ; i++) { linkedList.add(i); } long time3 = System.currentTimeMillis(); System.out.println(time3 - time2); } }
運行結果:(多次運行結果之後發現不一定誰插入快)
第二種情況,給ArrayList 設置長度。
public class test2 { public static void main(String[] args) { int index = 10000000; ArrayList arrayList = new ArrayList(index); LinkedList linkedList = new LinkedList(); long time0 = System.currentTimeMillis(); for (int i = 0; i < index ; i++) { arrayList.add(i); } long time1 = System.currentTimeMillis(); System.out.println(time1 - time0); long time2 = System.currentTimeMillis(); for (int i = 0; i < index ; i++) { linkedList.add(i); } long time3 = System.currentTimeMillis(); System.out.println(time3 - time2); } }
運行結果:
無法得出結論誰插入快。
我們來看看爲什麼ArrayList 插入也這麼快呢? 先來看看ArrayList 的源碼
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
先分析ArrayList 的對象定義,發現繼承AbstractList,實現 List<E>, RandomAccess, Cloneable, java.io.Serializable 四個接口。
AbstractList : 抽象類,定義了list的公共抽象方法。
List<E>: 接口, 定義了一些list的公共接口。
RandomAccess:標記接口,表示實現該接口的子類支持角標訪問,主要用於判斷list 是否實現該接口來知道是否可以通過下標訪問,做中間件非常常用。
Cloneable:標記接口,表示實現該接口的子類可以進行克隆:https://www.cnblogs.com/jssj/p/13767756.html
Serializable:標記接口,表示實現該接口的子類可以實現序列化和反序列化:https://www.cnblogs.com/jssj/p/11766027.html
接下來我們看看ArrayList的add方法的源碼
public boolean add(E e) { ensureCapacityInternal(size + 1); // 判斷list的長度夠不夠,不夠就擴容 elementData[size++] = e; //elementData 是list真正存儲數據的數組 return true; }
ok. 我們看看ArrayLIst是如何擴容的。
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 這一步是判斷ArrayList是在創建對象的時候是否傳入指定長度。沒有傳入指定長度的. return Math.max(DEFAULT_CAPACITY, minCapacity); // 則返回默認創建數組的長度,爲了後面可以直接擴容。 } return minCapacity; }
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) //判斷當前數組的長度是否夠用,不夠用,調用grow方法擴容。 grow(minCapacity); }
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; //獲取原長度 int newCapacity = oldCapacity + (oldCapacity >> 1); //擴大1.5倍得到新長度。 if (newCapacity - minCapacity < 0) //新獲得的長度是否小於要出入的位置,如果小,則直接擴大到需要插入位置的大小。(不知道什麼場景會進這裏) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) // 數組的長度不能超過Integer 的長度-8. 8 指的是數組本身也需要暫用的空間。 newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); // 複製數組。真正的複製邏輯是native本地方法。 }
指定位置添加元素:
public void add(int index, E element) { rangeCheckForAdd(index); // 判斷index是否有效 ensureCapacityInternal(size + 1); // 上面已經講過原理。 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 從該位置複製一份後面的值,全部往後移。 elementData[index] = element; // 最後在當前位置修改元素值。 size++; }
看看ArrayList 是如何刪除的:
public E remove(int index) { rangeCheck(index); // 判斷角標是否越界 modCount++; //操作計數器(用於迭代器迭代的時候如果這個list發送變化,能夠及時感知到,提前報錯,而不是獲取錯誤數據) 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; }
關於操作計數器(modCount),再通過案例說明一下
public class test2 { public static void main(String[] args) { int index = 100; ArrayList arrayList = new ArrayList(index); for (int i = 0; i < index ; i++) { arrayList.add(i); } Iterator<Integer> iterator = arrayList.iterator(); while (iterator.hasNext()){ int a = iterator.next(); if(a == 3){ arrayList.remove(a); // list 刪除了元素 } } } }
運行結果:
從案例中我們可以看到,迭代的過程中是不允許刪除或者添加元素的,修改沒有問題,要保證長度不變。
再來看一個案例
public class test2 { public static void main(String[] args) { long[] long1 = new long[]{1,2,3,5}; List arrayList1 = Arrays.asList(long1); System.out.println(arrayList1.size()); Long[] long2 = new Long[]{1l,2l,3l,5l}; List arrayList2 = Arrays.asList(long2); System.out.println(arrayList2.size()); } }
運行結果:
我們要注意基本數據類型是不支持泛型化的。所以數組轉list需要小心這種情況。
擴展知識,不可變集合
public class test2 { public static void main(String[] args) { // 不可變集合 List list = Collections.unmodifiableList(Arrays.asList("2","5","7")); list.add("9"); // 該操作是不允許的 } }
運行結果:
總結
1. ArrayList 的底層數據結構使用的是數組。
2. ArrayList是通過創建1.5倍長度的數組來進行擴容的。
3. ArrayList被迭代的時候是不能改變原list的長度的。
4. 使用Arrays.asList 方法的時候需要注意數組是否爲基本類型。