ArrayList源碼分析

整體介紹

ArrayList實(shí)現(xiàn)了List接口,是一個(gè)常見的集合類,它有一下特點(diǎn):

  • 是順序容器,即元素存放的數(shù)據(jù)與放進(jìn)去的順序相同,
  • 允許放入null元素温数,
  • 底層通過(guò)數(shù)組實(shí)現(xiàn)。
  • 添加元素而容量不足時(shí)蜻势,可自動(dòng)擴(kuò)充底層數(shù)組的容量撑刺。
  • 除該類未實(shí)現(xiàn)同步外,其余跟Vector大致相同握玛。
  • 操作時(shí)間復(fù)雜度分別為:
    • size,isEmpty,get,set,iterator,listIterator方法運(yùn)行時(shí)間為常量時(shí)間
    • add方法運(yùn)行為攤還常量時(shí)間够傍,也即增加n個(gè)元素的時(shí)間為O(n)
    • 其他的操作運(yùn)行時(shí)間大致為線性時(shí)間,常數(shù)因子相較于LinkedList更小挠铲。

攤還時(shí)間是一個(gè)操作的平均代價(jià)冕屯,可能某次操作代價(jià)很高,但總體的來(lái)看也并非那么糟糕拂苹;add方法是攤還常量時(shí)間與底層數(shù)組容量自動(dòng)擴(kuò)充有關(guān)

image.png

源碼分析

成員變量

    /**
     * 初始容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 當(dāng)ArrayList的空實(shí)例,用于無(wú)參數(shù)初始化
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * ArrayList的底層數(shù)組
     * 泛型只是編譯器提供的語(yǔ)法糖所以這里的數(shù)組是一個(gè)Object數(shù)組
     * ArrayList的容量就是elementData.length
     * 當(dāng)ArrayList為空時(shí),elementData == EMPTY_ELEMENTDATA
     * 當(dāng)?shù)谝粋€(gè)元素加入時(shí)elementData.length == DEFAULT_CAPACITY
     * transient 說(shuō)明這個(gè)數(shù)組無(wú)法序列化安聘。
     */
    private transient Object[] elementData;

    /**
     * ArrayList包含的元素?cái)?shù)量,與容量不同.
     */
    private int size;

    /**
     * 數(shù)組最大容量
     * 一些虛擬器需要在數(shù)組前加個(gè)頭標(biāo)簽,所以減去8 瓢棒。 
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 這個(gè)變量并不是ArrayList里面定義的,而是繼承自父類AbstractList
     * ArrayList在使用Iterator時(shí)發(fā)生不同步會(huì)拋出錯(cuò)誤就是依靠了這個(gè)變量
    (雖然不可靠,想要可靠的同步保證用
    List list = Collections.synchronizedList(new ArrayList(...))
    或
    concurrent包下的CopyOnWriteArrayList
    或
    用synchronized進(jìn)行一層代理)
     * add和remove方法都是有modCount++.
     * 下面再具體說(shuō)說(shuō)modCount的作用
     */
     protected transient int modCount = 0;

get()

get()方法本身很簡(jiǎn)單,不涉及數(shù)組的容量擴(kuò)充,除了檢查下標(biāo)范圍以及類型轉(zhuǎn)換,與一般的數(shù)組get()操作沒(méi)區(qū)別

   public E get(int index) {
        //檢查是否越界,
        //由于java數(shù)組本身就會(huì)檢查是否越界,所以這里只是檢查是否超過(guò)了size,
        //要知道數(shù)組的大小大于size,index大于size并不會(huì)觸發(fā)數(shù)組越界.
        rangeCheck(index);
        return elementData(index);
    }
    
    @SuppressWarnings("unchecked")
    E elementData(int index) {
        //對(duì)元素進(jìn)行類型轉(zhuǎn)換,底層儲(chǔ)存是Object,但返回是E.
        return (E) elementData[index];
    }

set()

set()方法也很簡(jiǎn)單,不涉及數(shù)組的容量擴(kuò)充,除了檢查下標(biāo)范圍,與一般的數(shù)組set()操作沒(méi)區(qū)別

    public E set(int index, E element) {
        //在get()中提到的,檢查越界問(wèn)題.
        rangeCheck(index);
        //直接用element替換elementData[index]
        //賦值到指定位置浴韭,復(fù)制的僅僅是引用
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

add()

  • 如果不算入容量擴(kuò)充的耗費(fèi),add()方法運(yùn)行時(shí)間為常量時(shí)間.但是可能某次add()觸發(fā)了容量擴(kuò)充,那么那次操作的運(yùn)行時(shí)間為線性時(shí)間.所以add()的運(yùn)行時(shí)間為攤還常量時(shí)間,即n次操作的時(shí)間為O(n).

  • add()會(huì)觸發(fā)modCount++

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    /**
     *從這個(gè)方法可知第一次add元素,初始容量為DEFAULT_CAPACITY,即10
     */
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
    /**
     *add()的modCount++來(lái)源于這里
     *當(dāng)新容量大于當(dāng)前容量時(shí),觸發(fā)容量擴(kuò)充,即調(diào)用grow方法.
     */
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    /**
     *ArrayList實(shí)現(xiàn)容量自動(dòng)擴(kuò)充是依靠了這個(gè)方法.
     *新容量為原容量的1.5倍
     *耗費(fèi)時(shí)間為O(n),因?yàn)樾枰獜?fù)制原來(lái)的元素到擴(kuò)充后的數(shù)組.
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //每次擴(kuò)充,新容量為原容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // //擴(kuò)展空間并復(fù)制
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

remove

  • remove運(yùn)行時(shí)間為O(n),因?yàn)锳rrayList底層實(shí)現(xiàn)為數(shù)組,所以刪除元素后,需要移動(dòng)之后的元素.
  • 除了越界檢查,與一般數(shù)組的remove沒(méi)有區(qū)別
  • remove后并不會(huì)影響數(shù)組容量
  • remove會(huì)觸發(fā)modCount++
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
       //這里清除了elementData[size]的引用,
       //因?yàn)榧偃绮磺宄?那么ArrayList就會(huì)一致保留那個(gè)對(duì)象
       //GC不能清除仍被持有引用的對(duì)象
        elementData[--size] = null;

        return oldValue;
    }

序列化

  • 在上面成員變量那里提到了Object[] elementData用了transient關(guān)鍵字,無(wú)法序列化,這里ArrayList復(fù)寫了readObject和writeObject方法,實(shí)現(xiàn)底層數(shù)組的序列化.
  • 復(fù)寫的writeObject用modCount保證了當(dāng)發(fā)生不同步問(wèn)題時(shí)拋出錯(cuò)誤(雖然不可靠)
  • 關(guān)于序列化的知識(shí)可以看我轉(zhuǎn)載的<<深入理解Java對(duì)象序列化>>
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // 記下方法開始時(shí)的modCount
        int expectedModCount = modCount;
        //將沒(méi)有transient關(guān)鍵字的成員變量寫入
        s.defaultWriteObject();

        //這里寫入數(shù)組的容量,但是把數(shù)組元素的數(shù)量size視為容量,
        //而不用原來(lái)的容量elementData.length
        s.writeInt(size);

        // 將底層數(shù)組的元素依次寫入
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        //與之前記下的modCount對(duì)比,看是否有其他線程操作了ArrayList,有則拋出錯(cuò)誤.
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // 將沒(méi)有transient關(guān)鍵字的成員變量讀入
        s.defaultReadObject();

        // 將數(shù)組容量讀入,
        //但是如上面writeObject所寫把size視為了數(shù)組容量,所以這里沒(méi)有實(shí)質(zhì)的作用
        s.readInt(); // ignored

        if (size > 0) {
            // 擴(kuò)充數(shù)組大小
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // 把數(shù)組元素一次寫入
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

迭代器

ArrayList用iterator()可以返回迭代器.

  • 實(shí)現(xiàn)了Iterator<E>接口;private class Itr implements Iterator<E>
  • 當(dāng)有多個(gè)線程同時(shí)操作ArrayList,使用迭代器可以拋出錯(cuò)誤,避免發(fā)生不同步(這里就是用了modCount,用法與writeObject()的差不多)
  • 迭代器使得對(duì)容器的遍歷操作完全與其底層相隔離,可以到達(dá)極好的解耦效果脯宿。
iterator()
    public Iterator<E> iterator() {
        //使用構(gòu)造函數(shù),返回一個(gè)新的迭代器
        return new Itr();
    }
迭代器成員變量
        int cursor;       // 下一個(gè)返回的元素的下標(biāo)
        int lastRet = -1; // 上一次返回的元素的下標(biāo),初始為-1
        //記下創(chuàng)建迭代器時(shí)的modCount,用于之后的同步檢查
        int expectedModCount = modCount;
hasNext()

hasNext()用于檢測(cè)是否有下一個(gè)元素返回,實(shí)現(xiàn)很簡(jiǎn)單.

        public boolean hasNext() {
            return cursor != size;
        }
checkForComodification()
  • 這個(gè)方法是迭代器可以在多個(gè)線程操作時(shí)拋出錯(cuò)誤(fail-fast機(jī)制)的關(guān)鍵方法
  • 下面的removenext都調(diào)用了這個(gè)方法
  • 實(shí)現(xiàn)很簡(jiǎn)單,對(duì)比modCount,如果不一致,則說(shuō)明有其他線程也在用ArrayList,拋出錯(cuò)誤.
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
next()
  • next()方法的實(shí)現(xiàn)很簡(jiǎn)單,代碼寫的很清楚
  • next()在內(nèi)部做了很多安全檢查,保證了使用迭代器的安全性
  • next() 調(diào)用了checkForComodification(),使用了fail-fast機(jī)制
        @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];
        }
remove()
  • 迭代器的remove()方法內(nèi)部是調(diào)用了ArrayList的remove()方法
  • remove() 調(diào)用了checkForComodification(),使用了fail-fast機(jī)制
        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();
            }
        }

后記

  • ArrayList的源代碼寫的很好,注釋也很清晰,我在這篇分析里面有很多內(nèi)容其實(shí)都是從注釋里翻譯,或者加了點(diǎn)自己的理解.
  • 這里只是挑了幾個(gè)我覺(jué)得重要的地方做了分析,其余的推薦直接看源代碼
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末念颈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子连霉,更是在濱河造成了極大的恐慌榴芳,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跺撼,死亡現(xiàn)場(chǎng)離奇詭異窟感,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)歉井,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門肌括,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事谍夭。” “怎么了憨募?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵紧索,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我菜谣,道長(zhǎng)珠漂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任尾膊,我火速辦了婚禮媳危,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冈敛。我一直安慰自己待笑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布抓谴。 她就那樣靜靜地躺著暮蹂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪癌压。 梳的紋絲不亂的頭發(fā)上仰泻,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天,我揣著相機(jī)與錄音滩届,去河邊找鬼集侯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛帜消,可吹牛的內(nèi)容都是我干的棠枉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼券犁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼术健!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起粘衬,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤荞估,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后稚新,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勘伺,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年褂删,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了飞醉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖缅帘,靈堂內(nèi)的尸體忽然破棺而出轴术,到底是詐尸還是另有隱情,我是刑警寧澤钦无,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布逗栽,位于F島的核電站,受9級(jí)特大地震影響失暂,放射性物質(zhì)發(fā)生泄漏彼宠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一弟塞、第九天 我趴在偏房一處隱蔽的房頂上張望凭峡。 院中可真熱鬧,春花似錦决记、人聲如沸摧冀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)按价。三九已至,卻和暖如春笙瑟,著一層夾襖步出監(jiān)牢的瞬間楼镐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工往枷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留框产,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓错洁,卻偏偏與公主長(zhǎng)得像秉宿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屯碴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

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

  • ArrayList是在Java中最常用的集合之一描睦,其本質(zhì)上可以當(dāng)做是一個(gè)可擴(kuò)容的數(shù)組,可以添加重復(fù)的數(shù)據(jù)导而,也支持隨...
    ShawnIsACoder閱讀 573評(píng)論 4 7
  • ArrayList 原文見:Java 容器源碼分析之 ArrayList 概述 ArrayList是使用頻率最高的...
    Leocat閱讀 232評(píng)論 0 0
  • 定義 除了實(shí)現(xiàn)了List接口忱叭,還實(shí)現(xiàn)了RandomAccess,Cloneable, java.io.Serial...
    zhanglbjames閱讀 430評(píng)論 0 0
  • List List是一個(gè)維持內(nèi)部元素有序的采集器今艺,其中的每個(gè)元素都會(huì)擁有一個(gè)索引韵丑,每個(gè)元素都可以通過(guò)他的索引獲取到...
    dooze閱讀 408評(píng)論 0 4
  • ArrayList 概述 ArrayList 是實(shí)現(xiàn)List接口的動(dòng)態(tài)數(shù)組,每個(gè)ArrayList實(shí)例都有一個(gè)默認(rèn)...
    ZcEDiaos閱讀 231評(píng)論 0 0