【java集合總結】-- ArrayList源碼解析

1、前言

  要想深刻的瞭解集合就必需要經過分析源碼來了解它,那如何來看源碼,要看什麼東西呢?主要從三個方面:html

  一、看繼承結構java

    看這個類的繼承結構,處於一個什麼位置,不須要背記,有個大概的感受就能夠,我本身感受了解了以後內心都舒服些。數組

  二、看構造方法安全

    很重要,通常在構造方法中會作不少事情,要跟蹤方法中的方法。dom

  三、看經常使用方法函數

    不必全部方法都去了解,知道經常使用、核心的方法實現便可。源碼分析

  本文參考:http://www.javashuo.com/article/p-dxhxlxrw-co.html性能

2、ArrayList概述

  1)ArrayList是能夠動態增加和縮減的索引序列,它是基於數組實現的List類this

  2)該類封裝了一個動態再分配的Object[]數組,每個類對象都有一個capacity屬性,表示它們所封裝的Object[]數組的長度,當向ArrayList中添加元素時,該屬性值會自動增長spa

  3)若是想ArrayList中添加大量元素,可以使用ensureCapacity方法一次性增長capacity,能夠減小增長重分配的次數提升性能。

  4)ArrayList的用法和Vector向相似,可是Vector是一個較老的集合,具備不少缺點,不建議使用。

    另外,ArrayList和Vector的區別是:ArrayList是線程不安全的,當多條線程訪問同一個ArrayList集合時,程序須要手動保證該集合的同步性,而Vector則是線程安全的。

  5)繼承關係圖:

  

3、源碼分析

3.一、繼承結構和層次關係

 

分析:

  爲何要讓AbstractList先實現List<E>,而後在讓ArrayList繼承AbstractList?爲何不讓ArrayList直接實現List<E>?

  這裏是一種默認的寫法,也能夠說是一種思想:讓AbstractList去實現接口中一些通用的方法,而具體的類ArrayList就繼承這個AbstractList類,拿到一些通用的方法,而後本身在實現一些本身特有的方法。

  這樣一來代碼更簡潔,而且若是有多個類繼承ArrayList,就能夠直接繼承ArrayList中通用的方法,減小重複代碼。因此通常看到一個類上面還有一個抽象類,應該就是這個做用。

3.二、類中屬性

  沒什麼可說的,看註釋便可。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable { // 版本號 private static final long serialVersionUID = 8683452581122892189L; // 缺省容量 private static final int DEFAULT_CAPACITY = 10; // 空對象數組 private static final Object[] EMPTY_ELEMENTDATA = {}; // 缺省空對象數組 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 元素數組 transient Object[] elementData; // 實際元素大小,默認爲0 private int size; // 最大數組容量 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; }

3.三、構造方法

 ArrayList有三個構造方法:

   一、無參構造函數

  

  DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一個空的Object[],將elementData初始化,elementData也是個Object[]類型。空的Object[]會給默認大小10,等會解釋何時賦值的。
  ArrayList中儲存數據的其實就是一個數組,這個數組就是elementData,下圖中有

  

  二、有參構造函數一

  邏輯很簡單,不解釋了。

  

  三、有參構成函數二(不經常使用)

  

  總結:arrayList的構造方法就作一件事情,就是初始化一下儲存數據的容器,其實本質上就是一個數組,在其中就叫elementData。

3.四、核心方法

 3.4.一、添加方法

  添加方法共有四個,這裏只介紹經常使用的兩種。

  1)boolean add(E);//默認直接在末尾添加元素

  

  分析:

  ensureCapacityInternal方法爲肯定容量方法。   
在添加元素以前須要肯定數組是否能放的下,size是數組中數據的個數,由於要添加一個元素,因此size+1。
  

  

  ensureCapacityInternal方法中分兩步:

  a、首先肯定最小容量:判斷elementData ==DEFAULTCAPACITY_EMPTY_ELEMENTDATA,即判斷初始化的elementData是否是空的數組。而後找出默認容量和參數容量中大的。

   

  b、調用ensureExplicitCapacity方法,該方法纔是真的判斷容量是否夠用的方法,若是不過用則擴容

  

  在ensureExplicitCapacity方法中,若是須要的容量大於elementData的容量,則調用grow方法進行擴容,grow方法是真正的擴容方法。

  至於modCount++這個做用不少,好比用來檢測快速失敗的一種標誌,這個對於咱們目前研究的問題無影響,不用在乎。

  private void grow(int minCapacity) { // overflow-conscious code
        int oldCapacity = elementData.length;  //將擴充前的elementData大小給oldCapacity
        int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity
        if (newCapacity - minCapacity < 0)//這句話就是適應於elementData就空數組的時候,length=0,那麼oldCapacity=0,newCapacity=0,因此這個判斷成立,在這裏就是真正的初始化elementData的大小了,就是爲10.前面的工做都是準備工做。
            newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0)//若是newCapacity超過了最大的容量限制,就調用hugeCapacity,也就是將能給的最大值給newCapacity
            newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //新的容量大小已經肯定好了,就copy數組,改變容量大小咯。
        elementData = Arrays.copyOf(elementData, newCapacity); }

  hugeCapacity();  

  //這個就是上面用到的方法,很簡單,就是用來賦最大值。
    private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();   //若是minCapacity都大於MAX_ARRAY_SIZE,那麼就Integer.MAX_VALUE返回,反之將MAX_ARRAY_SIZE返回。由於maxCapacity是三倍的minCapacity,可能擴充的太大了,就用minCapacity來判斷了。   //Integer.MAX_VALUE:2147483647 MAX_ARRAY_SIZE:2147483639 也就是說最大也就能給到第一個數值。仍是超過了這個限制,就要溢出了。至關於arraylist給了兩層防禦。
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }

   2)void add(int,E);在特定位置添加元素,也就是插入元素

public void add(int index, E element) { rangeCheckForAdd(index);//檢查index也就是插入的位置是否合理。 //跟上面的分析同樣,具體看上面
        ensureCapacityInternal(size + 1);  // Increments modCount!! //這個方法就是用來在插入元素以後,要將index以後的元素都日後移一位,
        System.arraycopy(elementData, index, elementData, index + 1, size - index); //在目標位置上存放元素
        elementData[index] = element; size++;//size增長1
    }  

  分析:

  rangeCheckForAdd方法

  

  注意:

    當調用空的構成函數建立ArrayList時,初始化List大小是在第一次添加時進行。

 3.4.二、刪除方法

  

  和添加方法同樣,這幾個刪除方法都是相似的,抽兩個分析下就行,其餘的都差很少。

  另外,fastRemove(int)方法是private的,是提供給remove(Object)這個方法用的。

   1)remove(int):經過刪除指定位置上的元素

public E remove(int index) { rangeCheck(index);//檢查index的合理性
 modCount++;//這個做用不少,好比用來檢測快速失敗的一種標誌。
        E oldValue = elementData(index);//經過索引直接找到該元素

        int numMoved = size - index - 1;//計算要移動的位數。
        if (numMoved > 0) //這個方法也已經解釋過了,就是用來移動元素的。
            System.arraycopy(elementData, index+1, elementData, index, numMoved); //將--size上的位置賦值爲null,讓gc(垃圾回收機制)更快的回收它。
        elementData[--size] = null; // clear to let GC do its work //返回刪除的元素。
        return oldValue; }

   2)remove(Object):這個方法能夠看出來,arrayList是能夠存放null值得。

  

  3)clear():將elementData中每一個元素都賦值爲null,等待垃圾回收將這個給回收掉,因此叫clear

   

  4)removeAll(collection c)批量刪除

  

  分析:

  batchRemove(xx,xx):

//這個方法,用於兩處地方,若是complement爲false,則用於removeAll若是爲true,則給retainAll()用,retainAll()是用來檢測兩個集合是否有交集的。
   private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; //將原集合,記名爲A
        int r = 0, w = 0;   //r用來控制循環,w是記錄有多少個交集
        boolean modified = false; try { for (; r < size; r++)      //參數中的集合C一次檢測集合A中的元素是否有,
                if (c.contains(elementData[r]) == complement)      //有的話,就給集合A
                    elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws.   //若是contains方法使用過程報異常
            if (r != size) {   //將剩下的元素都賦值給集合A,
 System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) {   //這裏有兩個用途,在removeAll()時,w一直爲0,就直接跟clear同樣,全是爲null。   //retainAll():沒有一個交集返回true,有交集但不全交也返回true,而兩個集合相等的時候,返回false,因此不能根據返回值來確認兩個集合是否有交集,而是經過原集合的大小是否發生改變來判斷,若是原集合中還有元素,則表明有交集,而元集合沒有元素了,說明兩個集合沒有交集。 // clear to let GC do its work
                for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; }
 3.4.三、indexOf方法
// 從首開始查找數組裏面是否存在指定元素
    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; }
3.4.四、get方法 

  

  要先檢測因此是否合法。

  注意:在get函數中存在element函數,element函數用於返回具體的元素,具體函數以下:

  

  說明:返回的值都通過了向下轉型(Object -> E),這些是對咱們應用程序屏蔽的小細節。

4、總結

  1)arrayList能夠存放null。
  2)arrayList本質上就是一個elementData數組。
  3)arrayList區別於數組的地方在於可以自動擴展大小,其中關鍵的方法就是gorw()方法。
  4)arrayList中removeAll(collection c)和clear()的區別就是removeAll能夠刪除批量指定的元素,而clear是刪除集合中的所有元素。
  5)arrayList因爲本質是數組,因此它在據的查詢方面會很快,而在插入刪除這些方面,性能降低不少,由於須要移動不少數據才能達到應有的效果  6)arrayList實現了RandomAccess,因此在遍歷它的時候推薦使用for循環。

相關文章
相關標籤/搜索