ArrayList
本質上是一個數組,它內部經過對數組的操做實現了List
功能,因此ArrayList
又被叫作動態數組.每一個ArrayList
實例都有容量,會自動擴容.它可添加null
,有序可重複,線程不安全.Vector
和ArrayList
內部實現基本是一致的,除了Vector
添加了synchronized
保證其線程安全.java
/** * 默認初始容量 */ private static final int DEFAULT_CAPACITY = 10; /** * 初始容量爲0時,elementData指向此對象(空元素對象) */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 調用ArrayList()構造方法時,elementData指向此對象(默認容量空元素對象) */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 用於存儲集合元素,非private類型可以簡化內部類的訪問(在編譯階段) */ transient Object[] elementData; /** * 包含的元素個數 */ private int size;
爲何DEFAULT_CAPACITY
這種變量爲何要聲明爲private static final
類型數組
private
是爲了把變量的做用範圍控制在類中.安全
static
修飾的變量是靜態變量.JVM
只會爲靜態變量分配一次內存.這樣不管對象被建立多少次,此變量始終指向的都是同一內存地址.達到節省內存,提高性能的目的.性能優化
final
修飾的變量在被初始化後,不可再被指向別的內存地址,以防變量的地址被篡改.併發
無參構造方法dom
public ArrayList() { //無參構造器方法,將elementData指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
指定容量構造方法oop
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { //initialCapacity大於0時,將elementData指向新建的initialCapacity大小的數組. this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //initialCapacity爲空時,將elementData指向EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); } }
指定集合構造方法源碼分析
public ArrayList(Collection<? extends E> c) { //將elementData指向c轉換後的數組 elementData = c.toArray(); if ((size = elementData.length) != 0) { //c.toArray 可能不會返回Object[],因此須要手動檢查下.關於這點,會單獨講解下,看3.3 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { //若是elementData.length爲0,將elementData指向EMPTY_ELEMENTDATA. this.elementData = EMPTY_ELEMENTDATA; } }
ArrayList
的無參構造方法使用頻率是很是高的,在第一次添加元素時,會將capacity
初始化爲10.在咱們知道ArrayList
中須要存儲多少元素時,使用指定容量構造方法,可避免擴容帶來的運行開銷,提升程序運行效率.當咱們須要複用Collection
對象時,使用指定集合構造方法.性能
add(E e)
源碼測試
//將指定的元素添加到列表的末尾 public boolean add(E e) { //確保內部容量,若是容量不夠則計算出所需的容量值. ensureCapacityInternal(size + 1); //將元素插入到數組尾部,size加一. elementData[size++] = e; return true; }
add(E e)
方法的平均時間複雜度是O(1)
.它的流程大致上分爲兩步:
第一步就是自動擴容機制,具體分析參看2.3.3.
第二步則是在確保有可用容量的基礎上,在尾部添加元素,以下圖:
add(int index, E element)
源碼
//在列表的指定位置上添加指定元素,在添加以前將在此位置上的元素及其後面的元素向右移一位. public void add(int index, E element) { //檢查索引是否越界 rangeCheckForAdd(index); //確保內部容量,若是容量不夠則計算出所需的容量值. ensureCapacityInternal(size + 1); // Increments modCount!! //將index及index以後的元素向右移一位. System.arraycopy(elementData, index, elementData, index + 1, size - index); //將新元素插入到index處. elementData[index] = element; //元素個數加一. size++; } //檢查索引是否越界 private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
add(int index, E element)
的平均時間複雜度是O(N)
.因此在大容量的集合中不要頻繁使用此方法,不然可能會產生效率問題.在指定位置添加元素的流程以下圖所示:
ensureCapacityInternal(int minCapacity)
源碼
//是否須要擴容 private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } //計算容量 private static int calculateCapacity(Object[] elementData, int minCapacity) { //若是elementData指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA,返回DEFAULT_CAPACITY和minCapacity中的較大值. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } //不然直接返回minCapacity return minCapacity; } //確保明確的容量 private void ensureExplicitCapacity(int minCapacity) { //修改次數加一 modCount++; //若是minCapacity大於elementData數組長度,那麼進行擴容. if (minCapacity - elementData.length > 0) grow(minCapacity); } //要分配的最大數組大小 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * 增長容量確保數組至少可以容納最小容量參數指定的元素個數. * * @param minCapacity 所需的最小容量 */ private void grow(int minCapacity) { //聲明oldCapacity爲elementData長度 int oldCapacity = elementData.length; //將newCapacity聲明爲oldCapacity的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); //若是newCapacity小於minCapacity,將newCapacity指向minCapacity if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //若是newCapacity超出了最大數組長度,調用hugeCapacity()方法計算newCapacity. if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //根據newCapacity生成一個新的數組,並將elementData老數據放入elementData中. elementData = Arrays.copyOf(elementData, newCapacity); } /** * * @param minCapacity 最小容量 * @return 計算後的容量 */ private static int hugeCapacity(int minCapacity) { //若是minCapacity小於0,拋出內存溢出異常. if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //比較得出所需容量 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
自動擴容的流程參看上面代碼,總結幾個要點:
new ArrayList()
構造方法,在添加第一個元素時,容量會被設置爲DEFAULT_CAPACITY
(10)大小.MAX_ARRAY_SIZE
,那麼將Integer.MAX_VALUE
做爲容量.remove(int index)
//移除指定索引位置元素 public E remove(int index) { //index越界檢查 rangeCheck(index); modCount++; //獲取將要刪除的元素 E oldValue = elementData(index); //獲取將要移動的元素個數 int numMoved = size - index - 1; if (numMoved > 0) //將index以後的元素向左移一位 System.arraycopy(elementData, index + 1, elementData, index, numMoved); //size減一,並將elementData數組最後一個元素指向null,讓GC進行操做 elementData[--size] = null; // clear to let GC do its work return oldValue; } @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; }
remove(Object o)
//刪除指定元素 public boolean remove(Object o) { //若是對象爲null,刪除數組中的第一個爲null的元素.沒有null元素的話則不會變化 if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { //刪除數組中的第一個爲o的元素,沒有則不操做. for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } //省略了越界檢查,並且不會返回被刪除的值,反映了JDK將性能優化到極致. 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; // clear to let GC do its work }
刪除操做的平均時間複雜度是O(N)
,其主要步驟是:
null
;size
減一;具體步驟以下圖所示:
ArrayList
實現了RandomAccess
接口.RandomAccess
是標識接口,標識實現類可以快速隨機訪問存儲元素.由於ArrayList
的底層是數組,經過下標index
訪問.因此在ArrayList
元素量較大時,應當使用普通for
循環,也就是經過下標進行訪問.而LinkedList
底層是鏈表,不具有快速隨機訪問的能力,所以沒有實現RandomAccess
接口,推薦使用forEach
遍歷(也就是Iterator
遍歷).
測試代碼:
@Test public void test6() { //實現了RandomAccess接口,底層是數組的ArrayList測試 List<Long> arrayList = new ArrayList<>(150000000); Random random = new Random(); //爲了更好的展現測試結果,避免程序運行時間過長,arrayList和linkedList添加元素的個數不一樣. for (int i = 0; i < 100000000; i++) { arrayList.add(random.nextLong()); } System.out.println("======ArrayList======"); traverseByLoop(arrayList); traverseByIterator(arrayList); System.out.println("======LinkedList======"); //沒有實現RandomAccess接口,底層是鏈表的LinkedList測試 LinkedList<Long> linkedList = new LinkedList<>(); for (int i = 0; i < 100000; i++) { linkedList.add(random.nextLong()); } traverseByLoop(linkedList); traverseByIterator(linkedList); } //普通for循環進行遍歷 public void traverseByLoop(List<Long> list) { long startTime = System.currentTimeMillis(); for (int i = 0; i < list.size(); i++) { list.get(i); } long endTime = System.currentTimeMillis(); System.out.println("RandomAccess遍歷用時:" + (endTime - startTime) + "ms"); } //Iterator遍歷 public void traverseByIterator(List<Long> list) { long startTime = System.currentTimeMillis(); Iterator<Long> iterator = list.iterator(); while (iterator.hasNext()) { iterator.next(); } long endTime = System.currentTimeMillis(); System.out.println("Iterator遍歷用時:" + (endTime - startTime) + "ms"); }
運行test6()
方法,控制檯輸出:
======ArrayList====== RandomAccess遍歷用時:4ms Iterator遍歷用時:10ms ======LinkedList====== RandomAccess遍歷用時:5572ms Iterator遍歷用時:3ms
fail-fast
機制在Iterator
被建立後,若是List
對象不是調用iterator
的remove()
或add(Object obj)
方法更改內部結構.iterator
就會拋出ConcurrentModificationException
.以此避免在迭代過程當中List
對象不可知的變化.這個機制只能用來偵測異常的操做,並不能做爲併發操做的保障.在JDK1.5
新增的forEach
循環,其本質就是用迭代器遍歷.下面用forEach
語法來對fail-fast
機制進行測試.
@Test public void test1() { List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3)); for (Integer num : list) { if (1 == num) { list.remove(num); } } System.out.println(list); }
因爲在遍歷過程當中調用了List
的remove()
方法,致使程序檢測到非法修改,拋出異常.
fail-fast
失效在forEach
中使用非iterator
方法刪除List
的倒數第二個元素,fail-fast
不會生效.
@Test public void test2() { List<Integer> list = new ArrayList<>(Arrays.asList(1, 2)); for (Integer num : list) { if (1 == num) { list.remove(num); } } System.out.println(list); }
forEach
是java
的語法糖,爲了搞清楚爲啥出現上面的問題,咱們將上面的代碼轉換爲Iterator
遍歷.
@Test public void test3() { List<Integer> list = new ArrayList<>(Arrays.asList(1, 2)); Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer next = iterator.next(); if (1 == next) { list.remove(next); } } System.out.println(list); }
首先看看list.iterator()
的源碼,看看這個Iterator
是啥?
public Iterator<E> iterator() { return new Itr(); }
Itr
類是ArrayList
的內部類,咱來看看源碼.
private class Itr implements Iterator<E> { //下一個要返回元素的索引,默認爲0. int cursor; //以前返回元素的索引,默認爲-1. int lastRet = -1; //保存建立時modCount的值 int expectedModCount = modCount; Itr() { } public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { //fail-fast檢測 checkForComodification(); //將i值聲明爲cursor int i = cursor; //index越界檢查 if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; //若是i值大於等於當前數組的長度,fail-fast if (i >= elementData.length) throw new ConcurrentModificationException(); //光標加一 cursor = i + 1; //對lastRet賦值並返回值. return (E) elementData[lastRet = i]; } //每次操做時比較expectedModCount和modCount的值,若不一致,拋出ConcurrentModificationException final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
從while (iterator.hasNext())
開始分析,第一次進入hasNext()
方法.cursor
爲0,size
爲2,返回true
,進入循環體.而後進入next()
方法,正常執行,cursor
變爲1,lastRet
變爲0.而後開始執行ArrayList
的remove()
方法,modCount
變爲1,size
變爲1.這時候再進入第二次循環,執行hasNext()
方法,cursor
是1,size
也是1,返回false
,退出循環體.所以fail-fast
失效.因此不要在forEach
循環中使用非Iterator
的方法進行增刪操做,fail-fast
也不能徹底避免數據被更改的風險,從源頭規避風險是首選.
c.toArray()
返回非Object[]
在ArrayList
的集合構造器源碼中有c.toArray might (incorrectly) not return Object[]
這句註釋.就上網查了下這個問題.咱們先來看下代碼:
@Test public void test5() { //獲取並輸出Object數組類型 Object[] objArr = new Object[0]; //class [Ljava.lang.Object; System.out.println(objArr.getClass()); //經過Arrays.asList()方法構建List對象.該對象的class不是Object[]類型. List<Integer> list1 = Arrays.asList(1, 2, 3); //class [Ljava.lang.Integer; System.out.println(list1.toArray().getClass()); //經過new ArrayList構造器建立List對象 ArrayList<Integer> list2 = new ArrayList<>(Arrays.asList(4, 5, 6)); //class [Ljava.lang.Object; System.out.println(list2.toArray().getClass()); }
控制檯輸出:
class [Ljava.lang.Object; class [Ljava.lang.Integer; class [Ljava.lang.Object;
能夠看到經過Arrays.asList(1, 2, 3)
建立的對象class
不是Object[]
類型.至於具體緣由再也不分析,感興趣的朋友研究下哈.
elementData
爲啥定義爲transient
ArrayList
本身根據size
序列化真實的元素,而不是根據數組的長度序列化元素,減小了空間佔用.