ArrayList也是在Java開發中使用頻率很是高的一個類,內部是基於數組的動態管理的方式來實現的。數組在內存裏面是一塊連續的存儲空間,其優點是基於下標的隨機訪問和遍歷是很是高效的。java
JDK8源碼中的ArrayList類結構定義以下:數組
class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
(1)繼承了AbstractList實現了List接口是一個數組隊列擁有了List基本的的增刪改查功能安全
(2)實現了RandomAccess接口擁有隨機讀寫的功能數據結構
(3)實現了Cloneable接口能夠被克隆多線程
(4)實現了Serializable了接口並重寫了序列化和反序列化方法,使得ArrayList能夠擁有更好的序列化的性能。併發
ArrayList中的成員變量和幾個構造方法以下:dom
//定義的序列化id,主要是爲了標識不一樣版本的兼容性 private static final long serialVersionUID = 8683452581122892189L; //默認的數組存儲容量 private static final int DEFAULT_CAPACITY = 10; //當指定數組的容量爲0的時候使用這個變量賦值 private static final Object[] EMPTY_ELEMENTDATA = {}; //默認的實例化的時候使用此變量賦值 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //真正存放數據的對象數組,並不被序列化 transient Object[] elementData; //數組中的真實元素個數它小於或等於elementData.length private int size; //數組中最大存放元素的個數 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //構造函數一,若是指定容量就分配指定容量的大小 //沒有指定就使用EMPTY_ELEMENTDATA賦值 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); } } //構造函數二,使用默認的DEFAULTCAPACITY_EMPTY_ELEMENTDATA賦值 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } //構造一個傳入的集合,做爲數組的數據 public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
在瞭解了它的成員變量和構造函數以後,咱們再來看下幾個經常使用的方法:函數
(一)添加工具
添加有兩個方法,第一個add(E e)方法的調用鏈涉及5個方法,分別以下:性能
//1 public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } //2 private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } //3 private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } //4 private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } //5 private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
這裏一步步分析,在調用了add(E e)的方法第一步,咱們看到了它調用了ensureCapacityInternal(size + 1)方法,在這個方法裏面首先判斷了數組是否是一個長度爲0的空數組,若是是的話就給它容量賦值爲默認的容量大小也就是10,而後調用了ensureExplicitCapacity方法,這個方法裏面記錄了modCount+1以後,並判斷了當前的容量是否小於數組當前的長度,若是大於當前數組的長度就開始進行擴容操做調用方法 grow(minCapacity),擴容的長度是增長了原來數組數組的一半大小,而後並判斷了是否達到了數組擴容的上限並賦值,接着把舊數組的數據拷貝到擴容後的新數組裏面再次賦值給舊數組,最後把新添加的元素賦值給了擴容後的size+1的位置裏面。
接着看第2個add方法:
public void add(int index, E element) { rangeCheckForAdd(index);//是否越界 ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
這裏面用到了 System.arraycopy方法,參數含義以下:
(原數組,原數組的開始位置,目標數組,目標數組的的開始位置,拷貝的個數)
(注:若是想了解關於Java裏面數組拷貝的幾種方式,請參考個人上一篇文章。)
這裏面主要是給指定位置添加一個元素,ArrayList首先檢查是否索引越界,若是沒有越界,就檢查是否須要擴容,而後將index位置以後的全部數據,總體拷貝到index+1開始的位置,而後就能夠把新加入的數據放到index這個位置,而index前面的數據不須要移動,在這裏咱們能夠看到給指定位置插入數據ArrayList是一項大動做比較耗性能。
(二)移除
(1)根據下標移除
public E remove(int 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-1的位置的元素賦值null,方便gc elementData[--size] = null; // clear to let GC do its work //最終返回舊的數據 return oldValue; }
(2)根據元素移除
public boolean remove(Object o) { //等於null值的移除 if (o == null) { //遍歷數組 for (int index = 0; index < size; index++) //找到集合裏面第一個等於null的元素 if (elementData[index] == null) { //而後移除 fastRemove(index); return true; } } else { //非null狀況下,遍歷每個元素經過equals比較 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; // clear to let GC do its work }
remove方法與add(int index, E element)正好是一個相反的操做過程,移除一個元素,會影響到一批數據的位置移動,因此也是比較耗性能的。
(三)查詢
public E get(int index) { //檢查是否越界 rangeCheck(index); //返回指定位置上的元素 return elementData(index); }
(四)修改
public E set(int index, E element) { //檢查是否越界 rangeCheck(index); //獲取舊的元素值 E oldValue = elementData(index); //新元素賦值 elementData[index] = element; //返回舊的元素值 return oldValue; }
(五)清空方法
public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
clear方法是把每一個元素的值賦值爲null,便於gc回收
(六)瘦身方法
public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
該方法主要將數組空間縮減,去掉數組裏面的null值。 Arrays.copyOf方法參數含義:(原數組,拷貝的個數)
(七)是否包含
public boolean contains(Object o) { return indexOf(o) >= 0; } 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; }
這裏面主要是分兩種狀況null值的遍歷和非null的遍歷遍歷,若是查詢到就返回下標位置,不然就返回-1,而後與0相比,大於0就存在,小於0就是不存在。
總結:
本文介紹了JDK8中的ArrayList的工做原理和經常使用方法分析,此外ArrayList非線程安全,因此須要多線程的場景下,請使用jdk自帶併發List結構或者Guava,Apache Common等工具包提供的List集合。基於數組實現的List在隨機訪問和遍歷的效率比較高,但在插入指定和刪除指定元素的時候效率比較低,而這正好和鏈表相反,鏈表的的查詢和隨機遍歷效率較低,但插入和刪除指定位置元素的效率比較高,這也是爲何HashMap中同時使用兩種數據結構來優點互補的緣由。