JDK8中ArrayList的工做原理剖析

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中同時使用兩種數據結構來優點互補的緣由。

相關文章
相關標籤/搜索