JAVA經常使用集合源碼解析系列-ArrayList源碼解析(基於JDK8)

文章系做者原創,若有轉載請註明出處,若有雷同,那就雷同吧~(who care!)java

1、寫在前面數組

這是源碼分析計劃的第一篇,博主準備把一些經常使用的集合源碼過一遍,好比:ArrayList、HashMap及其對應的線程安全實現,此文章做爲本身相關學習的一個小結,記錄學習成果的同時,也但願對有緣的朋友提供些許幫助。安全

固然,能力所限,不免有紕漏,但願發現的朋友可以予以指出,不勝感激,以避免誤導了你們!併發

2、穩紮穩打過源碼ide

首先,是源碼內部的成員變量定義以及構造方法:源碼分析

 1 /**
 2      * Default initial capacity.
 3     (默認初始化長度)ps:實際是「延時初始化」(lazy init),後文詳解
 4      */
 5     private static final int DEFAULT_CAPACITY = 10;
 6 
 7     /**
 8      * Shared empty array instance used for empty instances.
 9     (共享空數組,爲了追求效率)
10      */
11     private static final Object[] EMPTY_ELEMENTDATA = {};
12 
13     /**
14      * Shared empty array instance used for default sized empty instances. We
15      * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
16      * first element is added.
17     (區別於EMPTY_ELEMENTDATA,使用默認構造方法時,默認使用此空數組,再配合DEFAULT_CAPACITY共同實現lazy init)
18      */
19     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
20 
21     /**
22      * The array buffer into which the elements of the ArrayList are stored.
23      * The capacity of the ArrayList is the length of this array buffer. Any
24      * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
25      * will be expanded to DEFAULT_CAPACITY when the first element is added.
26     (集合數據真正存放的地方,因此對於ArrayList咱們能夠理解爲提供了一組高效操做方法的數組。注意註釋後半段:當集合的首個元素被添加時,把空集合DEFAULTCAPACITY_EMPTY_ELEMENTDATA擴展爲DEFAULT_CAPACITY大小的集合,這就是lazy init,使用時才分配內存空間,目的是防止空間的浪費。)ps:transient 表示此變量不參與序列化
27      */
28     transient Object[] elementData; // non-private to simplify nested class access
29 
30     /**
31      * The size of the ArrayList (the number of elements it contains).
32      *
33      * @serial
34     (數組大小)
35      */
36     private int size;
參數項
 1 /**
 2      * Constructs an empty list with the specified initial capacity.
 3      *
 4      * @param  initialCapacity  the initial capacity of the list
 5      * @throws IllegalArgumentException if the specified initial capacity
 6      *         is negative
 7 (沒什麼可說,初始化參數>0建立該長度數組,=0使用共享空數組,<0報錯)
 8      */
 9     public ArrayList(int initialCapacity) {
10         if (initialCapacity > 0) {
11             this.elementData = new Object[initialCapacity];
12         } else if (initialCapacity == 0) {
13             this.elementData = EMPTY_ELEMENTDATA;
14         } else {
15             throw new IllegalArgumentException("Illegal Capacity: "+
16                                                initialCapacity);
17         }
18     }
19 
20     /**
21      * Constructs an empty list with an initial capacity of ten.
22 (使用默認空數組,lazy init,添加元素時數組擴展至默認容量DEFAULT_CAPACITY)
23      */
24     public ArrayList() {
25         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
26     }
27 
28     /**
29      * Constructs a list containing the elements of the specified
30      * collection, in the order they are returned by the collection's
31      * iterator.
32      *
33      * @param c the collection whose elements are to be placed into this list
34      * @throws NullPointerException if the specified collection is null
35      */
36     public ArrayList(Collection<? extends E> c) {
37         elementData = c.toArray();
38         if ((size = elementData.length) != 0) {
39             // c.toArray might (incorrectly) not return Object[] (see 6260652)
40 (準確的說這是個bug,從下面連接能夠看到會在jdk9修復,bug產生緣由後面詳解。附上連接http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652)
41             if (elementData.getClass() != Object[].class)
42                 elementData = Arrays.copyOf(elementData, size, Object[].class);
43         } else {
44             // replace with empty array.
45             this.elementData = EMPTY_ELEMENTDATA;
46         }
47     }
構造方法

 進階分析,集合經常使用操做,先從簡單入手:性能

 1 /**
 2 思路很簡單:
 3 1.判斷index是否越界,越界則異常
 4 2.直接取出數組elementData相應位置index的元素並強轉爲E
 5 **/
 6 public E get(int index) {
 7         rangeCheck(index);
 8 
 9         return elementData(index);
10     }
11 private void rangeCheck(int index) {
12         if (index >= size)
13             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
14     }
15  E elementData(int index) {
16         return (E) elementData[index];
17     }
按位次獲取單個集合元素
 1 /**
 2 一樣很簡單:
 3 1.判斷index是否越界,越界則異常
 4 2.取到index位置的值做爲原值
 5 3.設置該位置值爲修改後的值
 6 4.return原值
 7 **/
 8 public E set(int index, E element) {
 9         rangeCheck(index);
10 
11         E oldValue = elementData(index);
12         elementData[index] = element;
13         return oldValue;
14     }
按位次修改單個集合元素

  如前所述,單個元素按位次獲取操做很簡單,只要你瞭解到arrayList內部是數組存放數據,那上述操做徹底能夠想獲得。學習

  下面繼續,集合的添加和刪除操做,沒看過源碼的朋友能夠思考下,添加和刪除操做同上述兩個操做主要的區別在哪裏?爲何會複雜些?測試

  由於添加元素的時候可能位置不夠嘛,那怎麼辦?位置不夠就加位置,也就是所謂的「擴容」!咱們本身思考下實現思路,而後同做者的思路對比下:ui

  1.上面咱們提到,默認構造方法使用的lazy_init模式,添加元素時才init。那第一步判斷是否須要init,須要則init。

  2.判斷是否須要擴容(通俗點說就是當前數組有沒有空位置?)須要就擴容(建立個容量更大的新數組),可是擴容也不是無限的,超過上限就報錯(Integer.Max_Value),並把原數據copy到新數組,不然什麼都不作

  3.在指定位置添加元素,並size++

 1 /**
 2 看下做者是如何思考的,和本身的思考作下對比:
 3 1.判斷是否須要lazy init,若是須要則init,而後執行步驟2
 4 2.判斷是否須要擴容,若是須要則執行步驟3,不然執行步驟5
 5 3.擴容,新數組長度=當前數組長度*1.5,並判斷擴容後長度是否知足目標容量,不知足,則新數組長度=目標容量。接着判斷新數組長度是否超過閾值MAX_ARRAY_SIZE,超過則執行步驟4
 6 4.目標數組長度=若是目標數組長度>MAX_ARRAY_SIZE?Integer.MAX_VALUE:MAX_ARRAY_SIZE
 7 5.擴容結束後,執行數據copy,從原數組copy數據到新數組
 8 6.在指定的位置添加元素,並使長度增長
 9 **/
10 public boolean add(E e) {
11         ensureCapacityInternal(size + 1);  // Increments modCount!!
12         elementData[size++] = e;
13         return true;
14     }
15 private void ensureCapacityInternal(int minCapacity) {
16         if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
17             minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
18         }
19 
20         ensureExplicitCapacity(minCapacity);
21     }
22 private void ensureExplicitCapacity(int minCapacity) {
23         modCount++;
24 
25         // overflow-conscious code
26         if (minCapacity - elementData.length > 0)
27             grow(minCapacity);
28     }
29 private void grow(int minCapacity) {
30         // overflow-conscious code
31         int oldCapacity = elementData.length;
32         int newCapacity = oldCapacity + (oldCapacity >> 1);
33         if (newCapacity - minCapacity < 0)
34             newCapacity = minCapacity;
35         if (newCapacity - MAX_ARRAY_SIZE > 0)
36             newCapacity = hugeCapacity(minCapacity);
37         // minCapacity is usually close to size, so this is a win:
38         elementData = Arrays.copyOf(elementData, newCapacity);
39     }
40 private static int hugeCapacity(int minCapacity) {
41         if (minCapacity < 0) // overflow
42             throw new OutOfMemoryError();
43         return (minCapacity > MAX_ARRAY_SIZE) ?
44             Integer.MAX_VALUE :
45             MAX_ARRAY_SIZE;
46     }
單個添加集合元素

  對比後發現,整體思路是沒問題的,可是做者爲何要加入下面這樣的容量上限處理邏輯?朋友們能夠思考下,ArrayList的容量上限究竟是Integer.MAX_VALUE仍是MAX_ARRAY_SIZE?

  答案是:Integer.MAX_VALUE,依然仍是它,並非源碼中做者定義的MAX_ARRAY_SIZE,那就引伸出一個問題了-MAX_ARRAY_SIZE存在的意義。

  個人理解是,這個上限的存在,減少了內存溢出的機率。首先註釋中也提到了「Some VMs reserve some header words in an array」,意思就是有些虛擬機把頭信息保留在數組中,毫無疑問保存信息是要佔空間的,也就是我實際數組空間多是不足Integer.MAX_VALUE的,做者應該是這樣的思路(我猜的~)「我能夠基本肯定實際空間不會小於MAX_ARRAY_SIZE(多是分析了主流虛擬機的實現而得出的結論),所以設置個閾值,來確保不會內存溢出,但若是這個容量仍是不夠,那把剩下的風險很高的8也給你好了,至因而不是會溢出,天知道,反正我已經盡力了!

if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
 1 /**
 2 比較簡單,一筆帶過,按位置刪除
 3 思路:
 4 1.越界檢測
 5 2.取出當前位置的元素
 6 3.當前位置以後的全部元素總體前移一位置
 7 4.最後位置置空,size--,返回刪除的元素值。
 8 **/
 9 public E remove(int index) {
10         rangeCheck(index);
11 
12         modCount++;
13         E oldValue = elementData(index);
14 
15         int numMoved = size - index - 1;
16         if (numMoved > 0)
17             System.arraycopy(elementData, index+1, elementData, index,
18                              numMoved);
19         elementData[--size] = null; // clear to let GC do its work
20 
21         return oldValue;
22     }
23 /**
24 思路很簡單,按元素刪除(注意只刪除從頭開始第一個匹配值),遍歷->匹配->刪除
25 值得注意的是,單獨對null作特殊處理,按地址比較
26 **/
27 public boolean remove(Object o) {
28         if (o == null) {
29             for (int index = 0; index < size; index++)
30                 if (elementData[index] == null) {
31                     fastRemove(index);
32                     return true;
33                 }
34         } else {
35             for (int index = 0; index < size; index++)
36                 if (o.equals(elementData[index])) {
37                     fastRemove(index);
38                     return true;
39                 }
40         }
41         return false;
42     }
43 
44 單個刪除集合元素
單個刪除元素
 1 public boolean removeAll(Collection<?> c) {
 2         Objects.requireNonNull(c);
 3         return batchRemove(c, false);
 4     }
 5 
 6     private boolean batchRemove(Collection<?> c, boolean complement) {
 7         final Object[] elementData = this.elementData;
 8         int r = 0, w = 0;
 9         boolean modified = false;
10         try {
11 //遍歷當前集合全部元素
12             for (; r < size; r++)
13                 if (c.contains(elementData[r]) == complement)
14 //若是指定集合不包含該元素(即不該刪除的,須要保留的),把當前元素移動到頭部w位置(原頭部元素因不符合條件,直接刪除掉,這裏覆蓋也即刪除),並把w標記移到下一位
15                     elementData[w++] = elementData[r];
16         } finally {
17             // Preserve behavioral compatibility with AbstractCollection,
18             // even if c.contains() throws.
19 //這兒兩種狀況:
20 //無異常r==size 不會進入這個if
21 //有異常,則把因異常而將來得及比較的全部元素總體copy到w位置,並把w標記移位size - r(能夠理解爲還未比較的數量)
22             if (r != size) {
23                 System.arraycopy(elementData, r,
24                                  elementData, w,
25                                  size - r);
26                 w += size - r;
27             }
28 //這兒很好理解,w位置(該位置以前都是比較或者異常而須要保留的)以後的全部都是應該刪除的。
29             if (w != size) {
30                 // clear to let GC do its work
31                 for (int i = w; i < size; i++)
32                     elementData[i] = null;
33                 modCount += size - w;
34                 size = w;
35                 modified = true;
36             }
37         }
38         return modified;
39     }
批量刪除
 1 /**
 2 看起來比較簡單,但仔細分析就會發現仍是有些深度的,有興趣能夠看下defaultReadObject的實現,這裏就暫時忽略,之後有機會詳細分析下java的序列化與反序列化。
 3 實現思路(以序列化爲例,反序列化同理)
 4 1.調用默認序列化
 5 2.寫入size
 6 3.遍歷數組,寫入全部集合元素
 7 **/
 8 private void writeObject(java.io.ObjectOutputStream s)
 9         throws java.io.IOException{
10         // Write out element count, and any hidden stuff
11         int expectedModCount = modCount;
12         s.defaultWriteObject();
13 
14         // Write out size as capacity for behavioural compatibility with clone()
15         s.writeInt(size);
16 
17         // Write out all elements in the proper order.
18         for (int i=0; i<size; i++) {
19             s.writeObject(elementData[i]);
20         }
21 
22         if (modCount != expectedModCount) {
23             throw new ConcurrentModificationException();
24         }
25     }
26 
27     /**
28      * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
29      * deserialize it).
30      */
31     private void readObject(java.io.ObjectInputStream s)
32         throws java.io.IOException, ClassNotFoundException {
33         elementData = EMPTY_ELEMENTDATA;
34 
35         // Read in size, and any hidden stuff
36         s.defaultReadObject();
37 
38         // Read in capacity
39         s.readInt(); // ignored
40 
41         if (size > 0) {
42             // be like clone(), allocate array based upon size not capacity
43             ensureCapacityInternal(size);
44 
45             Object[] a = elementData;
46             // Read in all elements in the proper order.
47             for (int i=0; i<size; i++) {
48                 a[i] = s.readObject();
49             }
50         }
51     }
序列化與反序列化
/**
迭代器
1.瞭解快速失敗機制便可
2.關注下forEachRemaining的實現,可擴展研究下lambda表達式
**/
public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
迭代器

3、 ArrayList使用建議

  1.當你須要肯定長度的ArrayList時,推薦使用該長度初始化,理由是防止頻繁擴容,擴容相對而言是對性能影響至關大的操做(請注意是相對而言,相對於常規的增刪改查等操做)。

  2.謹慎使用clone、toArray,對於源對象和方法返回對象,數據修改操做會相互影響。他們最終都執行了System.arrayCopy(),都是「淺拷貝」:只copy了引用,全部對於其中一個引用的修改操做會傳遞到其餘引用上。java的copy應該都是「淺拷貝」,測試以下:

/**
測試代碼,隨手爲之,僅供參考。
**/
class TestObj {

            TestObj(Integer i, String s) {
                this.i = i;
                this.s = s;
            }

            Integer i;

            String s;

            public String toString() {

                return "i=" + i + "," + "s=" + s;
            }
        }
        ArrayList<TestObj> arrayList = new ArrayList<>();

        int size = 10;


        for (Integer i = 0; i < size; i++) {

            arrayList.add(new TestObj(i, "test" + i));

        }


        ArrayList<TestObj> cloneArrayList = (ArrayList<TestObj>) arrayList.clone();

        cloneArrayList.add(new TestObj(101, "test" + 101));

        System.out.println("arrayList size:" + arrayList.size());
        System.out.println("cloneArrayList size:" + cloneArrayList.size());


        System.out.println("arrayList index 0:" + arrayList.get(0).toString());
        System.out.println("cloneArrayList index 0:" + cloneArrayList.get(0).toString());

        //修改cloneArrayList index 0
        TestObj testInteger = cloneArrayList.get(0);
        testInteger.i = 1000111;

        System.out.println("修改cloneArrayList index=0對象後,ps:我沒修改arrayList哦");


        System.out.println("arrayList index 0:" + arrayList.get(0).toString());
        System.out.println("cloneArrayList index 0:" + cloneArrayList.get(0).toString());
測試結果:
arrayList size:10
cloneArrayList size:11
arrayList index 0:i=0,s=test0
cloneArrayList index 0:i=0,s=test0
修改cloneArrayList index=0對象後,ps:我沒修改arrayList哦
arrayList index 0:i=1000111,s=test0
cloneArrayList index 0:i=1000111,s=test0
測試clone

  3.謹慎使用subList,深刻分析後會發現,本質緣由同上面相似,都是相同的引用,因此數據修改操做會相互影響,以下例子:

 1 public static void testSubList() {
 2 
 3         Integer index = 10;
 4 
 5         List<Integer> myList = new ArrayList<>(index);
 6 
 7         for (int i = 0; i < index; i++) {
 8             myList.add(i);
 9         }
10 
11         List<Integer> mySubList = myList.subList(3, 5);
12 
13 
14         System.out.println("打印myList:");
15         myList.forEach(System.out::println);
16 
17         System.out.println("對mySubList增長個元素,注意我沒對myList作任何操做哦");
18         mySubList.add(100);
19 
20 
21         System.out.println("打印mySubList:");
22         mySubList.forEach(System.out::println);
23 
24         System.out.println("再次打印myList:");
25         myList.forEach(System.out::println);
26 
27 
28     }
29 運行結果:
30 打印myList:
31 0
32 1
33 2
34 3
35 4
36 5
37 6
38 7
39 8
40 9
41 對mySubList增長個元素,注意我沒對myList作任何操做哦
42 打印mySubList:
43 3
44 4
45 100
46 再次打印myList:
47 0
48 1
49 2
50 3
51 4
52 100
53 5
54 6
55 7
56 8
57 9
測試subList

  4.細心的朋友可能發現了,arrayList沒有提供對數組的構造方法,可是咱們知道array->arrayList是比較常見的需求,那如何作呢?辦法不止一種,選擇你喜歡的便可,以下:

1 int[] ints = {1, 2, 3, 4};
2         List<Integer> myIntListByStream = Arrays.stream(ints).boxed().collect(Collectors.toList());
3 
4 
5         Integer[] integers = {1, 2, 3, 4};
6         List<Integer> myListByAsList = Arrays.asList(integers);   //ps:此方式獲得的數組不可修改,這裏的修改指的是全部更改List數據的行爲
7         List<Integer> myListByNewArrayListAsList = new ArrayList<>(Arrays.asList(integers));
8         List<Integer> myListByStream = Arrays.stream(integers).collect(Collectors.toList());
arrayToList

4、源碼相關擴展(我能想到的能夠深刻思考的一些地方)

  1.快速失敗(fail fast)機制:

  咱們知道,ArrayList不是線程安全的集合類。意味着在併發操做時,很容易發生錯誤。按照常規思路,發現結果出錯,拋出異常便可。但在實際應用中,咱們並不知足於此,有沒有一種檢測機制,在併發操做前進行校驗,提早告訴咱們可能發生的錯誤呢?如你所料,就是咱們提到的快速失敗機制。它是如何實現的呢?其實很簡單,咱們定義一個計數器modCount,這個計數器記錄全部集合結構變更的次數,好比增長兩個元素就modCount+=2,再好比刪除3個元素就modCount+=3,這個計數器只能增長不能減小,而後咱們在執行一些須要快速失敗的操做時(好比:迭代器、序列化等等),執行以前記錄下當前modCount爲expectedModCount,在適當的時候判斷expectedModCount、modCount是否相等就能夠判斷這期間是否有過集合結構的變更。

  2.lambda表達式:讀源碼咱們發現,ArrayList中加入了許多支持lambda的方法,做爲JDK8的亮點之一,應該可以熟練使用,而後再詳細分析lambda的實現機制我以爲也頗有意思。

  3.jdk中經常使用JNI方法的實現:上文咱們在對clone方法作分析的時候,最終只分析到Object的native方法,我以爲有機會去看下經常使用的一些native方法的實現也是頗有意思的,待研究。

5、總結一下

老實說,分析完ArrayList發現,所花的時間大出我預計,我一度認爲我已經理解的很透徹了,可是在寫這篇文章的途中我又把差很少一半的源碼回顧了一遍(這真是個悲傷的故事),不過無論怎麼說,這是個好的開始,後面一篇解析hashMap。

相關文章
相關標籤/搜索