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


一、前言

  要想深入的了解集合就必須要通過分析源碼來了解它,那如何來看源碼,要看什么東西呢?主要從三個方面:

  1、看繼承結構

    看這個類的繼承結構,處於一個什么位置,不需要背記,有個大概的感覺就可以,我自己感覺了解了之后心里都舒服些。

  2、看構造方法

    很重要,一般在構造方法中會做很多事情,要跟蹤方法中的方法。

  3、看常用方法

    沒必要所有方法都去了解,知道常用、核心的方法實現即可。

  本文參考:https://www.cnblogs.com/zhangyinhua/p/7687377.html#_label3_1_3_2

二、ArrayList概述

  1)ArrayList是可以動態增長和縮減的索引序列,它是基於數組實現的List類

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

  3)如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以減少增加重分配的次數提高性能。

  4)ArrayList的用法和Vector向類似,但是Vector是一個較老的集合,具有很多缺點,不建議使用。

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

  5)繼承關系圖:

  

三、源碼分析

3.1、繼承結構和層次關系

 

分析:

  為什么要讓AbstractList先實現List<E>,然后在讓ArrayList繼承AbstractList?為什么不讓ArrayList直接實現List<E>?

  這里是一種默認的寫法,也可以說是一種思想:讓AbstractList去實現接口中一些通用的方法,而具體的類ArrayList就繼承這個AbstractList類,拿到一些通用的方法,然后自己在實現一些自己特有的方法。

  這樣一來代碼更簡潔,並且如果有多個類繼承ArrayList,就可以直接繼承ArrayList中通用的方法,減少重復代碼。所以一般看到一個類上面還有一個抽象類,應該就是這個作用。

3.2、類中屬性

  沒什么可說的,看注釋即可。

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.3、構造方法

 ArrayList有三個構造方法:

   1、無參構造函數

  

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

  

  2、有參構造函數一

  邏輯很簡單,不解釋了。

  

  3、有參構成函數二(不常用)

  

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

3.4、核心方法

 3.4.1、添加方法

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

  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.2、刪除方法

  

  和添加方法一樣,這幾個刪除方法都是類似的,抽兩個分析下就行,其他的都差不多。

  另外,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.3、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.4、get方法 

  

  要先檢測所以是否合法。

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

  

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

四、總結

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM