首頁>技術>

前言:

既然是看原始碼那我們要怎麼看一個類的原始碼呢?這裡我推薦的方法是:

1)看繼承結構

看這個類的層次結構,處於一個什麼位置,可以在自己心裡有個大概的瞭解。

2)看構造方法

在構造方法中,看做了哪些事情,跟蹤方法中裡面的方法。

3)看常用的方法

跟構造方法一樣,這個方法實現功能是如何實現的

注:既然是原始碼,為什麼要這樣設計類,有這樣的繼承關係。這就要說到設計模式的問題了。所以我們要了解常用的設計模式,才能更深刻的去理解這個類。

一:ArrayList簡介1.1、ArrayList概述

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

該類封裝了一個動態再分配的Object【】陣列,每一個類物件都有一個capacity屬性,表示他們所封裝的Object【】陣列長度,當向ArrayList中新增元素時,該屬性值會自動增加。如果想在ArrayList中新增大量元素,可使用ensureCapacity方法一次性新增capacity,可以減少增加重分配的次數,提高效能。

ArrayList的用法和Vector向類似,但是Vector是一個較老的集合,具有很多缺點,不建議使用。另外,ArrayList和Vector的區別是:ArrayList是執行緒不安全的,當多條執行緒訪問同一個ArrayList集合時,程式需要手動保證該集合的同步性,而Vector則是執行緒安全的。

ArrayList的資料結構

分析一個類的時候,資料結構往往是它的靈魂所在,理解底層的資料結構其實就理解了該類的實現思路,具體的實現細節再具體分析。

ArrayList的資料結構是:

說明:底層的資料結構就是陣列,陣列元素型別為Object型別,即可以存放所有型別資料。我們對ArrayList類的例項的所有的操作底層都是基於陣列的。

二、ArrayList原始碼分析2.1、繼承結構和層次關係
public class ArrayList<E> extends AbstractList<E>        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {

分析:

為什麼要先繼承AbstractList,而讓AbstractList先實現List<E>?而不是讓ArrayList直接實現List<E>?

這裡是有一個思想,介面中全都是抽象的方法,而抽象類中可以有抽象方法,還可以有具體的實現方法,正是利用了這一點,讓AbstractList是實現介面中一些通用的方法,而具體的類,

如ArrayList就繼承這個AbstractList類,拿到一些通用的方法,然後自己在實現一些自己特有的方法,這樣一來,讓程式碼更簡潔,就繼承結構最底層的類中通用的方法都抽取出來,

先一起實現了,減少重複程式碼。所以一般看到一個類上面還有一個抽象類。   

2.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; // non-private to simplify nested class access    //實際元素大小,預設為0    private int size;    //最大陣列容量    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
2.3、構造方法

ArrayList有三個構造方法:

1)無參構造方法  
/** * 預設會給10的大小,所以一開始arrayList的容量就是10 */public ArrayList() {    //是個空的Object[],將elementData初始化,elementData也是個Object[]型別。空的object[]會預設賦值為10,後面會提到什麼時候賦值    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

備註:

transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 2)有參建構函式一
public ArrayList(Collection<? extends E> c) {    //轉換為陣列    elementData = c.toArray();    //判斷陣列中的資料個數    if ((size = elementData.length) != 0) {        // c.toArray might (incorrectly) not return Object[] (see 6260652)        //每個集合的toArray()的實現方法不一樣,所以需要判斷一下,如果不是Object[].class型別,那麼就要使用ArrayList方法去改造下。        if (elementData.getClass() != Object[].class)            elementData = Arrays.copyOf(elementData, size, Object[].class);    } else {        // replace with empty array.        this.elementData = EMPTY_ELEMENTDATA;    }}

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

2.4、核心方法2.4.1、add()方法(有四個)

1)boolean add(E);//預設直接在末尾新增元素

//新增一個特定的元素到list末尾public boolean add(E e) {    //確定內部容量是否夠了,size是陣列中資料的個數,因為要新增一個元素,所以size+1,先判斷size+1這個個數在陣列中是否放的下,就在這個方法中去判斷是否陣列.length是否夠用了。    ensureCapacityInternal(size + 1);  // Increments modCount!!    //在資料中正確的位置放上元素e,並且size++    elementData[size++] = e;    return true;}

分析:ensureCapacityInternal(xxx); 確定內部容量的方法   

private void ensureCapacityInternal(int minCapacity) {    //先判斷初始化的elementData是否為空陣列,也就是沒有長度    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {        //因為如果是空的化,minCapacity =size+1;其實就是等於1,空的陣列沒有長度就存不了了,所以就將minCapacity變成10,也就是預設大小,但是在這裡,還沒有真正初始化這個elementData的大小        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);    }    //確認實際容量,上面只是將minCapacity =10,這個方法就是真正的判斷elementData是否夠用    ensureExplicitCapacity(minCapacity);}

ensureExplicitCapacity(xxx);

private void ensureExplicitCapacity(int minCapacity) {    modCount++;    //判斷minCapacity 如果大於了實際elementData的長度,那麼就說明elementData陣列的長度不夠用,不夠用那麼就要增加elementData的length。    //1、minCapacity=size+1=1——>minCapacity=10,elementData.length =0    //2、minCapacity=size+1=11,elementData.length=10,結果>0就要去擴容,這裡舉個例子    if (minCapacity - elementData.length > 0)        //自動擴充套件大小        grow(minCapacity);}

grow(xxx); arrayList核心的方法,能擴充套件陣列大小的真正秘密。

private void grow(int minCapacity) {    // 將擴容前的elementData大小給oldCapacity    int oldCapacity = elementData.length;    //newCapacity =(1.5*oldCapacity)    int newCapacity = oldCapacity + (oldCapacity >> 1);    //newCapacity =0,minCapacity =10    if (newCapacity - minCapacity < 0)        //newCapacity =10        newCapacity = minCapacity;     //如果newCapacity超過了最大容量限制,就呼叫hugeCapacity,也就是將給的最大值給newCapacity    if (newCapacity - MAX_ARRAY_SIZE > 0)        newCapacity = hugeCapacity(minCapacity);    // 新的容量大小已經確定好了,就copy陣列,改變容量大小了    elementData = Arrays.copyOf(elementData, newCapacity);}

hugeCapacity();

//用來賦最大值private static int hugeCapacity(int minCapacity) {    if (minCapacity < 0) // overflow        throw new OutOfMemoryError();  //兩層防護    return (minCapacity > MAX_ARRAY_SIZE) ?        //Integer.MAX_VALUE:2147483647        Integer.MAX_VALUE :        //MAX_ARRAY_SIZE=2147483639        MAX_ARRAY_SIZE;}

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

public void add(int index, E element) {    //檢測index,也就是插入的位置是否合理    rangeCheckForAdd(index);    //分析過了    ensureCapacityInternal(size + 1);  // Increments modCount!!    //這個方法就是用在插入元素之後,要將index之後的元素都往後移動一位    System.arraycopy(elementData, index, elementData, index + 1,                     size - index);    //在目標位置上存存放元素    elementData[index] = element;    //size+1    size++;}

分析:rangeCheckForAdd(index)  

private void rangeCheckForAdd(int index) {    //插入的位置肯定不能大於size和小於0    if (index > size || index < 0)        //如果是,則陣列越界異常        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}

System.arraycopy(...):就是將elementData在插入位置後的所有元素往後面移一位。

public static native void arraycopy(Object src,  int  srcPos,                                    Object dest, int destPos,                                    int length);
src:源物件srcPos:源物件物件的起始位置dest:目標物件destPost:目標物件的起始位置length:從起始位置往後複製的長度。
總結:    

正常情況下會擴容1.5倍,特殊情況下(新擴充套件陣列大小已經達到了最大值)則只取最大值。

當我們呼叫add方法時,實際上的函式呼叫如下:

說明:程式呼叫add,實際上還會進行一系列呼叫,可能會呼叫到grow,grow可能會呼叫hugeCapacity。

舉例說明一: 

ArrayList<Integer> lists = new ArrayList<Integer>();lists.add(8);

說明:初始化lists大小為0,呼叫的ArrayList()型建構函式,那麼在呼叫lists.add(8)方法時,會經過怎樣的步驟呢?下圖給出了該程式執行過程和最初與最後的elementData的大小。

說明:我們可以看到,在add方法之前開始elementData = {};呼叫add方法時會繼續呼叫,直至grow,最後elementData的大小變為10,之後再返回到add函式,把8放在elementData[0]中。

舉例說明二:  

ArrayList<Integer> lists = new ArrayList<Integer>(6);lists.add(8);

說明:呼叫的ArrayList(int)型建構函式,那麼elementData被初始化為大小為6的Object陣列,在呼叫add(8)方法時,具體的步驟如下:

說明:我們可以知道,在呼叫add方法之前,elementData的大小已經為6,之後再進行傳遞,不會進行擴容處理。

public E remove(int index) {    //檢測index的合理性    rangeCheck(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值得。

//透過元素來刪除該元素,就依次遍歷,就將該元素的索引傳給fastRemove(index),使用這個方法來刪除該元素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;}
//和remove方法實現差不多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}

3)clear():將elementData中每個元素都賦值為null,等待垃圾回收將這個給回收掉,所以叫clear

/** * Removes all of the elements from this list.  The list will * be empty after this call returns. */public void clear() {    modCount++;    // clear to let GC do its work    for (int i = 0; i < size; i++)        elementData[i] = null;    size = 0;}

4)removeAll(collection c):

public boolean removeAll(Collection<?> c) {    Objects.requireNonNull(c);    //批次刪除    return batchRemove(c, false);}

5)batchRemove(xx,xx):用於兩個方法,一個removeAll():它只清楚指定集合中的元素,retainAll()用來測試兩個集合是否有交集。 

private boolean batchRemove(Collection<?> c, boolean complement) {    //將原集合記為A    final Object[] elementData = this.elementData;    //r用來控制迴圈,w是記錄有多少個交集    int r = 0, w = 0;    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) {            // 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;}

總結::remove函式使用者移除指定下標的元素,此時會把指定下標到陣列末尾的元素向前移動一個單位,並且會把陣列最後一個元素設定為null,

2.4.3、set()方法
public E set(int index, E element) {    //檢測索引是否合法    rangeCheck(index);    //舊值    E oldValue = elementData(index);    //賦新值    elementData[index] = element;    //返回舊值    return oldValue;}

說明:設定指定下標索引的元素值

2.4.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;}

說明:從頭開始查詢與指定元素相等的元素,注意,是可以查詢null元素的,意味著ArrayList中可以存放null元素的。與此函式對應的lastIndexOf,表示從尾部開始查詢。

2.4.5、get()方法
public E get(int index) {    //檢測索引合法性    rangeCheck(index);    return elementData(index);}

說明:get函式會檢查索引值是否合法(只檢查是否大於size,而沒有檢查是否小於0),值得注意的是,在get函式中存在element函式,element函式用於返回具體的元素,具體函式如下:

E elementData(int index) {    return (E) elementData[index];}

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

三、總結 

1、arrayList是可以存放null。

2、arrayList本質就是一個elementData陣列。

3、arrayList區別於陣列的地方在於能夠自動擴充套件大小,其中關鍵方法就是grow()方法

6、arrayList實現了RandomAccess,所以在遍歷它的時候推薦使用for迴圈

補充:RandomAccess介面:這個是一個標記性介面,透過檢視api文件,它的作用就是用來快速隨機存取,有關效率的問題,在實現了該介面的話,那麼使用普通的for迴圈來遍歷,效能更高,例如arrayList。

文章有幫助可以點個「收藏」或「分享」,分享不易,請兄弟姐妹們點下吧!

8
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 開源許可(BSD、Apache、GPL)