Java ArrayList類源碼分析

前言

  在前面的學習集合中只是介紹了集合的相關用法,我們想要更深入的去了解集合那就要通過我們去分析它的源碼來了解它。希望對集合有一個更進一步的理解!

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

    1)看繼承結構

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

    2)看構造方法

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

    3)看常用的方法

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

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

一、ArrayList簡介

1.1、ArrayList概述

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

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

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

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

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

  4)ArrayList與Collection的關係:

                         

1.2、ArrayList的數據結構

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

  ArrayList的數據結構是:

    

  說明:底層的數據結構就是數組,數組元素類型爲Object類型,即可以存放所有類型數據。我們對ArrayList類的實例的所有的操作底層都是基於數組的。

二、ArrayList源碼分析

2.1、繼承結構和層次關係

  

  

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}

 分析:

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

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

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

      先一起實現了,減少重複代碼。所以一般看到一個類上面還有一個抽象類,應該就是這個作用。

    2)ArrayList實現了哪些接口?

      List<E>接口:我們會出現這樣一個疑問,在查看了ArrayList的父類AbstractList也實現了List<E>接口,那爲什麼子類ArrayList還是去實現一遍呢?

            這是想不通的地方,所以我就去查資料,有的人說是爲了查看代碼方便,使觀看者一目瞭然,說法不一,但每一個讓我感覺合理的,但是在stackOverFlow中找到了答案,這裏其實很有趣。

            網址貼出來 http://stackoverflow.com/questions/2165204/why-does-linkedhashsete-extend-hashsete-and-implement-sete開發這個collection 的作者Josh說。

            這其實是一個mistake,因爲他寫這代碼的時候覺得這個會有用處,但是其實並沒什麼用,但因爲沒什麼影響,就一直留到了現在。

      RandomAccess接口:這個是一個標記性接口,通過查看api文檔,它的作用就是用來快速隨機存取,有關效率的問題,在實現了該接口的話,那麼使用普通的for循環來遍歷,性能更高,例如arrayList

                而沒有實現該接口的話,使用Iterator來迭代,這樣性能更高,例如linkedList。所以這個標記性只是爲了讓我們知道我們用什麼樣的方式去獲取數據性能更好。

      Cloneable接口:實現了該接口,就可以使用Object.Clone()方法了。

      Serializable接口:實現該序列化接口,表明該類可以被序列化,什麼是序列化?簡單的說,就是能夠從類變成字節流傳輸,然後還能從字節流變成原來的類。

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;
    // 實際元素大小,默認爲0
    private int size;
    // 最大數組容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}

2.3、構造方法

  ArrayList有三個構造方法:

    

  1)無參構造方法:

 /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    transient Object[] elementData; // 非私有,簡化嵌套類獲取

/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;  是個空的Object[], 將elementData初始化,elementData也是個Object[]類型。空的Object[]會給默認大小10,等會會解釋什麼時候賦值的。
    }

 2)有參構造函數一:

/**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};



/**
     * 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) {
        //將自定義的容量大小當成初始化elementData的大小,這裏將initialCapacity等於0的情況列出來
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

 3)有參構造方法三(不常用)

//這個構造方法不常用,舉個例子就能明白什麼意思
//Strudent exends Person
//ArrayList<Person>、 Person這裏就是泛型
//我還有一個Collection<Student>、由於這個Student繼承了Person,那麼根據這個構造方法,我就可以把這個Collection<Student>轉換爲ArrayList<Person>這就是這個構造方法的作用 

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // 防止c.toArray(錯誤地)不返回Object []
            //每個集合的toarray()的實現方法不一樣,所以需要判斷一下,如果不是Object[].class類型,那麼久需要使用ArrayList中的方法去改造一下。
            // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
            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);//默認直接在末尾添加元素

    //表示此列表被結構修改的次數  
    //結構修改是指更改列表大小或以其他方式干擾列表的方式,即正在進行的迭代可能會產生錯誤的結果
    protected transient int modCount = 0;


/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        modCount++;   
        add(e, elementData, size);   //調用另外的方法
        return true;
    }

  2)void add(E , Object[] , int); //在Object數組的指定位置添加元素

/**
     * 此輔助方法從add(E)中分離出來,以使方法的字節碼大小保持在35以下(-XX:MaxInlineSize默認值),這有助於在C1編譯循環中調用add(E)
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }


    private Object[] grow() {
        return grow(size + 1);
    }

    /**
     * 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
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity)); //elementData是原始數組,第二個參數是newLength
    }

    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {  //因爲minCapacity是最小界限,所以要先考慮
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow,當0XXXXXXX..變爲1XXXXXXXX..的時候就是上溢
                throw new OutOfMemoryError();   //minCapacity不能超過有符號整數的最大限制
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0) //MAX_ARRAY_SIZE = Integer.MAX_VALUE-8
            ? newCapacity
            : hugeCapacity(minCapacity);
    }


    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE)
            ? Integer.MAX_VALUE    //當minCapacity處在MAX_ARRAY_SIZE-Integer.MAX_VALUE之間時用Integer.MAX_VALUE代替
            : MAX_ARRAY_SIZE;
    }


    

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

public void add(int index, E element) {
        rangeCheckForAdd(index);  //檢查index也就是插入的位置是否合理
        modCount++;
        final int s;
        Object[] elementData;
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow();
        //這個方法就是用來在插入元素之後,要將index之後的元素都往後移一位,
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);
        //在目標位置上存放元素
        elementData[index] = element;
        size = s + 1;  //size增加1
    }

這裏關於Arrays.copyOf與System.arraycopy的區別:https://mp.csdn.net/postedit/102293911

private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

我們可以測試一下向ArrayList中添加元素時,它的capacity變化,capacity即element數組的大小,而element數組又是私有的,於是想到利用反射訪問

public class ArrayListTest {
    public static void main(String[] args) {
        ArrayList<Integer> lists = new ArrayList<>();
        ArrayList<Integer> arrayList = new ArrayList<>();

        System.out.println(getArrayListCapacity(arrayList));

        //增加元素,使其擴容
        arrayList.add(0);
        System.out.println(getArrayListCapacity(arrayList));

        for(int i = 0; i < 10; ++i)
            arrayList.add(0);
        System.out.println(getArrayListCapacity(arrayList));

        for(int i = 0; i < 5; ++i)
            arrayList.add(0);
        System.out.println(getArrayListCapacity(arrayList));

        //輸出結果一次爲:0  10  15  22
    }

    public static int getArrayListCapacity(ArrayList<?> arrayList) {
        Class<ArrayList> arrayListClass = ArrayList.class;
        try {
            Field field = arrayListClass.getDeclaredField("elementData");
            field.setAccessible(true);
            Object[] objects = (Object[])field.get(arrayList);
            return objects.length;
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            return -1;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return -1;
        }
    }
    
}

 2.4.2、刪除方法

    其實這幾個刪除方法都是類似的。我們選擇幾個講,其中fastRemove(int)方法是private的,是提供給remove(Object)這個方法用的。

    

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

    public E remove(int index) {
        Objects.checkIndex(index, size);
        final Object[] es = elementData;

        @SuppressWarnings("unchecked") E oldValue = (E) es[index];
        fastRemove(es, index);

        return oldValue;
    }


/**
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(Object[] es, int i) {
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)    //判斷是不是要刪除最後一個元素
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }

  2)remove(Object):這個方法可以看出來,arrayList是可以存放null值的

public boolean remove(Object o) {
        final Object[] es = elementData;
        final int size = this.size;
        int i = 0;
        //先找到o所在位置的索引
        found: {
            if (o == null) {
                for (; i < size; i++)
                    if (es[i] == null)
                        break found;    //這裏表示ArrayList裏可以存放null值
            } else {
                for (; i < size; i++)
                    if (o.equals(es[i]))   //Obejct.equals(Object obj){ return this == obj};
                        break found;
            }
            return false;
        }
        fastRemove(es, i);
        return true;
}

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++;
        final Object[] es = elementData;
        for (int to = size, i = size = 0; i < to; i++)  //代碼很簡潔
            es[i] = null;    
    }

思考:爲什麼在以上大部分方法中在對elementData[]做改變時,都要先用final Object[]來做引用,而不是直接在原數組上操作,而且在後面都跟隨着modCount++的變化?

 4)removeAll(collection c):

public boolean removeAll(Collection<?> c) {
        return batchRemove(c, false, 0, size);     //批量刪除
    }

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

//這個方法,用於兩處地方,如果complement爲false,則用於removeAll;如果爲true,則給retainAll()用,retainAll()是用來檢測兩個集合是否有交集的。
boolean batchRemove(Collection<?> c, boolean complement,
                        final int from, final int end) {
        
        Objects.requireNonNull(c);  //檢查指定的對象引用c不是null
        final Object[] es = elementData;
        int r;         //r用來控制循環,w是記錄有多少個交集
        // Optimize for initial run of survivors
        for (r = from;; r++) {
            if (r == end)
                return false;
            if (c.contains(es[r]) != complement)  //參數中的集合C一次檢測集合es中的元素是否有
                break;
        }
        int w = r++;  w是記錄第一次滿足上面if判斷的位置,然後將後續元素不滿足上面if判斷的元素複製到w位置,且w再次後移
        try {
            for (Object e; r < end; r++)
                if (c.contains(e = es[r]) == complement)
                    es[w++] = e;
        } catch (Throwable ex) {
            // 即使c.contains()拋出異常,仍保留與AbstractCollection的行爲兼容性
            System.arraycopy(es, r, es, w, end - r);
            w += end - r;
            throw ex;
        } finally {
            modCount += end - w;
            shiftTailOverGap(es, w, end);  通過向下滑動以下元素來消除從w到end的間隙
        }
        return true;
    }

 總結::remove函數用戶移除指定下標的元素,此時會把指定下標到數組末尾的元素向前移動一個單位,並且會把數組最後一個元素設置爲null,

      這樣是爲了方便之後將整個數組不被使用時,會被GC,可以作爲小的技巧使用。

2.4.3、set()方法

 /**
     * Replaces the element at the specified position in this list with
     * the specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
         // 檢驗索引是否合法
        Objects.checkIndex(index, size);
        E oldValue = elementData(index);   //elementData(index) == elementData[index]
        elementData[index] = element;
        return oldValue;  //返回舊值
    }

2.4.4、indexOf()方法

/**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index {@code i} such that
     * {@code Objects.equals(o, get(i))},
     * or -1 if there is no such index.
     */
    public int indexOf(Object o) {
        return indexOfRange(o, 0, size);  // 從首開始查找數組裏面是否存在指定元素
    }

    int indexOfRange(Object o, int start, int end) {
        Object[] es = elementData;
        if (o == null) {
            for (int i = start; i < end; i++) {
                if (es[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = start; i < end; i++) {
                if (o.equals(es[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

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

2.4.5、get()方法

public E get(int index) {
        Objects.checkIndex(index, size);   檢查index是否在0(包括)到length(不包括)的範圍內
        return elementData(index);
    }

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

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

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

思考:爲什麼ArrayList的elementData是用transient修飾的?

transient修飾的屬性意味着不會被序列化,也就是說在序列化ArrayList的時候,不序列化elementData。

爲什麼要這麼做呢?

  1. elementData不總是滿的,每次都序列化,會浪費時間和空間

  2. 重寫了writeObject 保證序列化的時候雖然不序列化全部 但是有的元素都序列化

所以說不是不序列化 而是不全部序列化。

三、總結 

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章