ArrayList源碼解析(jdk1.7)

1.什么是ArrayList

  • 使用動態(tài)數(shù)組Object[]保存元素陨簇,允許null值
  • 實(shí)現(xiàn)List接口两入,允許使用下標(biāo)操作元素
  • 非線程安全鸭轮,線程安全參見Vector
  • 適用于頻繁訪問的場景赚哗,不合適頻繁插入或者刪除(參見LinkedList)
  • 支持批量操作

2.ArrayList的數(shù)據(jù)結(jié)構(gòu)

2.1 類定義

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

  • 繼承AbstractList:實(shí)現(xiàn)了List,對List中重要操作的實(shí)現(xiàn)稿蹲,包括add,remove等
  • 實(shí)現(xiàn) RandmoAccess 接口:支持快速隨機(jī)訪問鹊奖,即通過元素的序號快速獲取元素對象
  • 實(shí)現(xiàn)Cloneable:重寫clone()苛聘,能被克隆(淺拷貝)
  • 實(shí)現(xiàn)Serializable:支持序列化

2.2 重要的全局變量

可以思考一下忠聚,如果讓我們來設(shè)計(jì)ArrayList设哗,需要哪些全局變量和函數(shù)?

  • Object[] 數(shù)組两蟀,用來存放元素
  • size:數(shù)組中元素個數(shù)
  • MaxSize:數(shù)組的最大長度
  • add方法(考慮擴(kuò)容)网梢,get方法,remove方法(考慮減容)赂毯,size()方法战虏,isEmpty()方法

jdk的實(shí)現(xiàn)拣宰,全局變量包括:

>//Default initial capacity.
    private static final int DEFAULT_CAPACITY = 10;
//Shared empty array instance used for empty instances.
    private static final Object[] EMPTY_ELEMENTDATA = {};
// 存放元素的數(shù)組,空的ArrayList烦感,即elementData = EMPTY_ELEMENTDATA徐裸,在添加第一個元素時,會擴(kuò)容到DEFAULT_CAPACITY 啸盏。transient 標(biāo)記序列化時不需要序列化該字段重贺。
    transient Object[] elementData;
// 元素個數(shù)size
    private int size;
//父類AbstractList中的成員變量,記錄集合被修改的次數(shù)回懦,用于迭代時的異常檢查(下文會詳細(xì)講解)
  protected transient int modCount = 0;

2.3 重要的函數(shù)

2.3.1 add方法

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

添加一個元素到集合尾部气笙,首先對數(shù)組大小進(jìn)行檢查,需要擴(kuò)容的進(jìn)行擴(kuò)容怯晕,然后將元素添加到尾部下標(biāo)所在位置潜圃,并將size加1。

private void ensureCapacityInternal(int minCapacity) {
      // 如果數(shù)組為空舟茶,則選擇DEFAULT_CAPACITY與當(dāng)前需要的總?cè)萘康妮^大者谭期,作為擴(kuò)容的參數(shù)
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        //若需要的容量比數(shù)組的長度還大,則需要進(jìn)行擴(kuò)容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    // 新的參考容量為舊容量的1.5倍(向上取整)吧凉,和hashmap的2倍不一樣
    //等同于 (int)Math.floor(oldCapacity*1.5) 
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)//若新的參考容量比需要的容量小隧出,則直接使用需要的容量
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
       //進(jìn)行數(shù)組拷貝,引用直接指向新數(shù)組阀捅,原數(shù)組被拋棄胀瞪,等待回收
         elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

2.3.2 remove()方法

刪除指定位置的元素

   public E remove(int index) {
        if (index >= size)//檢查下標(biāo)是否合法
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        modCount++;//修改modCount
        E oldValue = (E) elementData[index];//拿到需要刪除的元素
        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
        return oldValue;
    }

2.3.3 sort()方法

    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }

注意:ArrayList的排序采用的是折半插入排序。

     * Sorts the specified portion of the specified array using a binary
     * insertion sort.  This is the best method for sorting small numbers
     * of elements.  It requires O(n log n) compares, but O(n^2) data
     * movement (worst case).
* @param a the array in which a range is to be sorted
     * @param lo the index of the first element in the range to be sorted
     * @param hi the index after the last element in the range to be sorted
     * @param start the index of the first element in the range that is
     *        not already known to be sorted ({@code lo <= start <= hi})
     */
    @SuppressWarnings({"fallthrough", "rawtypes", "unchecked"})
    private static void binarySort(Object[] a, int lo, int hi, int start) {
        assert lo <= start && start <= hi;
        if (start == lo)
            start++;
        for ( ; start < hi; start++) {
            Comparable pivot = (Comparable) a[start];

            // Set left (and right) to the index where a[start] (pivot) belongs
            int left = lo;
            int right = start;
            assert left <= right;
            /*
             * Invariants:
             *   pivot >= all in [lo, left).
             *   pivot <  all in [right, start).
             */
            while (left < right) {
                int mid = (left + right) >>> 1;
                if (pivot.compareTo(a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
            }
            assert left == right;

            /*
             * The invariants still hold: pivot >= all in [lo, left) and
             * pivot < all in [left, start), so pivot belongs at left.  Note
             * that if there are elements equal to pivot, left points to the
             * first slot after them -- that's why this sort is stable.
             * Slide elements over to make room for pivot.
             */
            int n = start - left;  // The number of elements to move
            // Switch is just an optimization for arraycopy in default case
            switch (n) {
                case 2:  a[left + 2] = a[left + 1];
                case 1:  a[left + 1] = a[left];
                         break;
                default: System.arraycopy(a, left, a, left + 1, n);
            }
            a[left] = pivot;
        }
    }

2.3.4 writeObject()方法

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();
        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);
        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }
       if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();

這里不直接使用s.writeObject(elementData)的方法饲鄙,是因?yàn)榭紤]到數(shù)組有一些空余的空間不需要序列化凄诞。

2.3.5 兩個toArray方法

  • 1
public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
  • 2
public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

如果toArray()需要向下轉(zhuǎn)型(例如將Object[]轉(zhuǎn)換為的Integer[]),則需要使用第2種方式忍级,傳入一個子類數(shù)組(如下代碼段)帆谍。否則直接使用第1種方法。由于java不支持向下轉(zhuǎn)型轴咱,第1種方式會拋java.lang.ClassCastException異常汛蝙。

public static Integer[] toArray(ArrayList<Integer> arrayList) {
    Integer[] newArrayList = (Integer[])arrayList.toArray(new Integer[0]);
    return newArrayList;
}

2.4 內(nèi)部類

ArrayList的內(nèi)部類
  • 靜態(tài)內(nèi)部類:ArrayListSpliterator可分割的迭代器,用于并行遍歷集合嗦玖。(jdk1.8才有)

靜態(tài)內(nèi)部類的使用場景是: 1 當(dāng)外部類需要是用內(nèi)部類患雇,而內(nèi)部類無需外部類的資源,并且內(nèi)部類可以單獨(dú)創(chuàng)建的時候宇挫,會考慮使用靜態(tài)內(nèi)部類苛吱。2 內(nèi)部類與外部類關(guān)系密切的,且不依賴外部類實(shí)例的器瘪,都可以使用靜態(tài)內(nèi)部類翠储。
例如builder模式中的builder類绘雁;hashmap的static class HashMapEntry<K,V>類。

  • private迭代器:Itr和ListItr
  • private SubList:集合的子集

2.5 其它

2.5.1 modCount與Fail-Fast機(jī)制

modCount記錄數(shù)組被修改的次數(shù)援所,作為線程不安全的集合類的成員變量庐舟,主要用在迭代器以及序列化writeObject()等場景。由下面迭代器代碼可以看到住拭,每次獲取元素之前都會檢查當(dāng)前modCount與該迭代器對象初始化時的modCount是否一致挪略,如果不一致,則拋出異常滔岳。這就是Fail-Fast機(jī)制杠娱。對于線程不安全的集合類,通過modCount域快速檢查集合內(nèi)容是否有變化谱煤。JDK為了提示開發(fā)者將非線程安全的類使用到并發(fā)的場景下時摊求,拋出一個異常,盡早發(fā)現(xiàn)代碼中的問題刘离。當(dāng)然室叉,這種機(jī)制并不一定有效。首先硫惕,jdk1.7中取消了modCount的volatitle修飾符茧痕,失去了各個線程直接對modCount的可見性。
注意:
在使用迭代器對象時疲憋,是可以修改數(shù)據(jù)的凿渊。即一下代碼是合法的。

while (iterator.hasNext()) {    
  if(iterator.next().equals("c")) {        
    iterator.remove("c");    
  }
}

public boolean hasNext() {
            return cursor < limit;
        }

原因在于:hasNext()方法并沒有使用modCount進(jìn)行判斷缚柳,而是比較下一個元素的位置與數(shù)組總長度。

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();
        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);
        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }
       if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
private class Itr implements Iterator<E> {
        // The "limit" of this iterator. This is the size of the list at the time the
        // iterator was created. Adding & removing elements will invalidate the iteration
        // anyway (and cause next() to throw) so saving this value will guarantee that the
        // value of hasNext() remains stable and won't flap between true and false when elements
        // are added and removed from the list.
        protected int limit = ArrayList.this.size;

        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 < limit;
        }

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

2.5.2 System.arrayCopy() Arrays.copyOf()的區(qū)別

1 public static native void arraycopy(Object src, int srcPos,Object dest, int destPos, int length);
參數(shù)依次對應(yīng)為:源數(shù)組搪锣,源數(shù)組copy的起始位置秋忙,目標(biāo)數(shù)組,目標(biāo)數(shù)組存放的起始位置构舟,需要copy的數(shù)組長度

2 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType)
參數(shù)依次對應(yīng)為:源數(shù)組灰追,新數(shù)組長度,新數(shù)組元素類型

實(shí)際上狗超, Arrays.copyOf()里面還是通過調(diào)用System.arrayCopy()實(shí)現(xiàn)最終的數(shù)組元素拷貝弹澎。所以,Arrays.copyOf()實(shí)際上是受限的System.arrayCopy()努咐。
區(qū)別主要為:

  • 1 System方法需要傳入新的數(shù)組苦蒿,Arrays方法不用(會在內(nèi)部自己新建一個數(shù)組并返回)。
  • 2 System方法沒有返回值渗稍,Arrays方法返回新的數(shù)組
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末佩迟,一起剝皮案震驚了整個濱河市团滥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌报强,老刑警劉巖灸姊,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異秉溉,居然都是意外死亡力惯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進(jìn)店門召嘶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來父晶,“玉大人,你說我怎么就攤上這事苍蔬∮战ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵碟绑,是天一觀的道長俺猿。 經(jīng)常有香客問我,道長格仲,這世上最難降的妖魔是什么押袍? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮凯肋,結(jié)果婚禮上谊惭,老公的妹妹穿的比我還像新娘。我一直安慰自己侮东,他們只是感情好圈盔,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悄雅,像睡著了一般驱敲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宽闲,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天众眨,我揣著相機(jī)與錄音,去河邊找鬼容诬。 笑死娩梨,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的览徒。 我是一名探鬼主播狈定,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吱殉!你這毒婦竟也來了掸冤?” 一聲冷哼從身側(cè)響起厘托,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎稿湿,沒想到半個月后铅匹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饺藤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年包斑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涕俗。...
    茶點(diǎn)故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡罗丰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出再姑,到底是詐尸還是另有隱情萌抵,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布元镀,位于F島的核電站绍填,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏栖疑。R本人自食惡果不足惜讨永,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望遇革。 院中可真熱鬧卿闹,春花似錦、人聲如沸萝快。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揪漩。三九已至量窘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間氢拥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工锨侯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嫩海,地道東北人。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓囚痴,卻偏偏與公主長得像叁怪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子深滚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容