1、ArrayList概述數組
首先咱們來講一下ArrayList是什麼?它解決了什麼問題?ArrayList實際上是一個數組,可是有區別於通常的數組,它是一個能夠動態改變大小的動態數組。ArrayList的關鍵特性也是這個動態的特性了,ArrayList的設計初衷就是爲了解決Java數組長度不可變的問題。咱們都知道在Java中數組一旦被建立出來,那麼這個數組的大小就不能夠改變了,並且初始化的時候就必需要指定數組的大小。在開發的場景中不少時候咱們並不知道咱們的數據量有多少,若是數組建立得太大就會形成極大的空間資源的浪費,若是過小了又會報數組下標越界異常。這是咱們在開發的過程當中使用數組來存儲數據時常常會遇到的問題,可是若是使用ArrayList的話,就能夠輕易的解決這個問題,它可以根據你存放的數據的大小動態的改變數組的大小(本質建立新數組),因此咱們在寫代碼的時候就不須要關心數組的大小了,我以爲這也是ArrayList建立出來所須要解決的問題。固然,若是在知道數組的大小且對數組的數據不增長的話,建議使用數組的存儲數據,這樣對性能的提高有必定的幫助。安全
那麼,ArrayList是怎麼動態擴展數組大小的呢?下面經過源碼一步一步揭開ArrayList的神祕面紗吧。多線程
2、ArrayList全局變量app
/** * Default initial capacity. */ //默認的初始化大小 private static final int DEFAULT_CAPACITY = 10; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ //這個是用來存放數據用的數組,add進來的數據都是放到這個數組裏面的 transient Object[] elementData; // non-private to simplify nested class access /** * The size of the ArrayList (the number of elements it contains). * * @serial */ //數組的大小 private int size;
3、ArrayList構造函數函數
ArrayList的構造函數有三個:性能
一、ArrayList(int initialCapacity):initialCapacity,數組的初始化大小,該構造器建立一個指定大小的空數組。ui
二、ArrayList():建立一個默認大小爲0的空數組this
三、ArrayList(Collection<? extends E> c):建立一個與c同樣大小的數組,並將c的元素賦值到數組中。線程
** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) {//建立一個 initialCapacity大小的空數組 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() {//建立空數組 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection<? extends E> c) { //將c轉換成數組,toArray方法返回的數組類型有可能不是Object類型的 elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) //數組類型不是Object類型,須要將類型轉換成Object類,避免後面調用方法 // 的時候出現類型轉換異常 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
這裏咱們主要看一下Arrays.copyOf()方法是如何轉換類型的:設計
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
這個方法中,若是傳入的類型爲Object類型,那麼就直接建立一個Object數組,不然建立一個對應類型的數組。而後調用System.arraycopy方法,將原數組賦值到新建立的數組中,強調一下,凡是使用到數組的地方就必定會使用到arraycopy的方法,這個方法我在String源碼閱讀有說到過,在這裏我再跟你們說一下這個方法吧。
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
這是一個本地方法因此沒法繼續往下看源碼了,可是從源碼中能夠知道每一個參數表明的含義
src:原數組
srcPos:原數組中開始複製的位置
dest:新數組,即目標數組
destPos:目標數組存放的位置,即從原數組的複製過來的元素從這個位置開始放
length:複製數組的長度
舉個代碼示例:
public static void main(String[] args) { int[] src = {1,2,3,4,5}; int[] dest = new int[6]; for (int i : dest) { System.out.print(i + " "); } System.out.println(); System.arraycopy(src,0,dest,0,src.length); for (int i : dest) { System.out.print(i + " "); } } //運行結果: //0 0 0 0 0 0 //1 2 3 4 5 0
看示例代碼應該可以懂,第一次打印的時候dest只是被初始化沒有賦值,因此裏面每一個元素存放的都是默認值,而int的默認值爲0,因此打印出來的都是0,以後arraycopy後將src的全部數據複製到dest從0位置開始,因此打印的結果是 1 2 3 4 5 0
4、add方法
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { //確認當前數組大小不會發生越界異常,否者對數組進行擴容 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } /** * Inserts the specified element at the specified position in this * list. Shifts the element currently at that position (if any) and * any subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException {@inheritDoc} */ public void add(int index, E element) { //檢查傳入的下標是否在數組的範圍以內 rangeCheckForAdd(index); //檢查數組是否須要擴容 ensureCapacityInternal(size + 1); // Increments modCount!! //在index位置的元素所有日後移一位,爲添加進來的元素騰出一個位置 System.arraycopy(elementData, index, elementData, index + 1, size - index); //將元素放入到index位置上 elementData[index] = element; size++; }
添加元素以前都須要檢查一下當前的數組是否已經滿了,若是滿了就按照添加元素後的大小進行擴容。能夠說在整個ArrayList類中最核心的方法就是ensureCapacityInternal了,這個方法就是用來動態擴容的。
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private void ensureExplicitCapacity(int minCapacity) { //修改次數+1,主要實現快速失敗機制的 modCount++; // overflow-conscious code //若是最小所需容量比當前數組的長度大的話就進行擴容 if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ 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; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //建立一個新的數組,長度爲 newCapacity,而後將原來數組的元素複製到新數組上並返回新數組,達到動態擴容數組的目的 elementData = Arrays.copyOf(elementData, newCapacity); }
在每次添加數據的時候都須要檢查一下添加數據後的長度是否大於當前數組的長度,大於的話就建立一個大小爲原來數組的1.5倍的新數組,而後將原數組的數據都複製到新數組中,最後將原數組的引用指向新數組,從而達到了擴容的目標。
5、get方法
/** * Returns the element at the specified position in this list. * * @param index index of the element to return * @return the element at the specified position in this list * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { rangeCheck(index); return elementData(index); } // Positional Access Operations @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; }
獲取數據的方法比較簡單,直接根據數組的下標index找到元素就好了,因此查找的速度比較快。
6、delete方法
/** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). * * @param index the index of the element to be removed * @return the element that was removed from the list * @throws IndexOutOfBoundsException {@inheritDoc} */ 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); elementData[--size] = null; // clear to let GC do its work return oldValue; } /** * Removes the first occurrence of the specified element from this list, * if it is present. If the list does not contain the element, it is * unchanged. More formally, removes the element with the lowest index * <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt> * (if such an element exists). Returns <tt>true</tt> if this list * contained the specified element (or equivalently, if this list * changed as a result of the call). * * @param o element to be removed from this list, if present * @return <tt>true</tt> if this list contained the specified element */ public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } /* * Private remove method that skips bounds checking and does not * return the value removed. */ 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 }
刪除有兩個重載的方法,一個是傳入一個數組的下標,另外一個是傳入具體的內容,可是最總仍是根據equals方法查到index,而後根據index刪除元素。假設有個數組爲 String[] strs = {"a","b","c","d","e"},如今調用 remove(3),那個大概流程爲:
7、ArrayList的使用:
public static void main(String[] args) { List<Integer> list = new ArrayList<>(); //添加操做 list.add(1); list.add(2); list.add(3); //刪除操做 list.remove(0); //查詢操做 //一、隨機訪問: for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } //加強for循環遍歷 for (Integer integer : list) { System.out.println(integer); } //使用迭代器遍歷 Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()){ Integer integer = iterator.next(); System.out.println(integer); } }
8、總結:
一、ArrayList的增刪改查等全部操做,其內部都是對數組進行操做。
二、ArrayList的動態擴容其本質是建立一個更大的數組。
三、每次擴容都擴大到原來的1.5倍,1.5倍是比較理想的,若是每次擴容過小的話就會頻繁擴容,每次擴容都須要進行數組的複製,性能消耗比較大,應該儘可能避免。若是擴容的倍數太大可能會形成空間的浪費。
四、ArrayList容許存放null值。
9、建議:
一、在知道數據大小且後期不會在添加數據的狀況下建議使用數組,而不是ArrayList;
二、初始化前估計數據量的大小,儘可能指定ArrayList的初始化容量,避免進行頻繁的數組複製操做;
三、在刪除比較多場景中不建議使用ArrayList;
四、在查多刪少的場景中建議使用ArrayList,緣由是這個類查找的速度很是快,時間複雜度爲O(1),即無論數據量有多大,查找速度都同樣的,並且很是快。可是刪除操做是比較慢的,上面我也有提到過,ArrayList中每一次刪除一個數據,後面全部的元素都要往前挪一位。若是數據量很是大,刪除的數據恰好在第一個位置,那個後面的全部元素都要前移,時間複雜度爲O(N),這樣是很是耗費時間的。
五、ArrayList是非線程安全的,若是在多線程環境中對安全的要求比較高的,建議使用 CopyOnWriteArrayList這個類,不懂得能夠百度一下,或者將ArrayList轉成線程安全得,使用這種方式就能夠:List list = Collections.synchronizedList(new ArrayList());
有興趣能夠加一下854630135這個羣去交流一下噢