ArrayList實(shí)現(xiàn)原理分析(Java源碼剖析)

  • ArrayList使用的存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)
  • ArrayList的初始化
  • ArrayList是如何動(dòng)態(tài)增長(zhǎng)
  • ArrayList如何實(shí)現(xiàn)元素的移除
  • ArrayList小結(jié)

ArrayList是我們經(jīng)常使用的一個(gè)數(shù)據(jù)結(jié)構(gòu)狼钮,我們通常把其用作一個(gè)可變長(zhǎng)度的動(dòng)態(tài)數(shù)組使用闷旧,大部分時(shí)候曲梗,可以替代數(shù)組的作用蛔琅,我們不用事先設(shè)定ArrayList的長(zhǎng)度,只需要往里不斷添加元素即可,ArrayList會(huì)動(dòng)態(tài)增加容量。ArrayList是作為L(zhǎng)ist接口的一個(gè)實(shí)現(xiàn)胎源。
那么ArrayList背后使用的數(shù)據(jù)結(jié)構(gòu)是什么呢?
ArrayList是如何保證動(dòng)態(tài)增加容量屿脐,使得能夠正確添加元素的呢涕蚤?

要回答上面的問(wèn)題,我們就需要對(duì)ArrayList的源碼進(jìn)行一番分析的诵,深入了解其實(shí)現(xiàn)原理的話万栅,我們就自然能夠解答上述問(wèn)題。

需要說(shuō)明的是奢驯,本文所分析的源碼引用自JDK 8版本

ArrayList使用的存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)

從源碼中我們可以發(fā)現(xiàn)申钩,ArrayList使用的存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)是Object的對(duì)象數(shù)組次绘。
其實(shí)這也不能想象瘪阁,我們知道ArrayList是支持隨機(jī)存取的類(lèi)似于數(shù)組,所以自然不可能是鏈表結(jié)構(gòu)邮偎。

/**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

我想大家一定對(duì)這里出現(xiàn)的transient關(guān)鍵字很疑惑管跺,我們都知道ArrayList對(duì)象是可序列化的,但這里為什么要用transient關(guān)鍵字修飾它呢禾进?查看源碼豁跑,我們發(fā)現(xiàn)ArrayList實(shí)現(xiàn)了自己的readObject和writeObject方法,所以這保證了ArrayList的可序列化泻云。具體序列化的知識(shí)我們?cè)诖瞬贿^(guò)多贅述艇拍。有興趣的讀者可以參考筆者關(guān)于序列化的文章狐蜕。

ArrayList的初始化

ArrayList提供了三個(gè)構(gòu)造函數(shù)。下面我們依次來(lái)分析

  • public ArrayList(int initialCapacity) 當(dāng)我們初始化的時(shí)候卸夕,給ArrayList指定一個(gè)初始化大小的時(shí)候层释,就會(huì)調(diào)用這個(gè)構(gòu)造方法。
List<String> myList = new ArrayList<String>(7);

源碼中這個(gè)方法的實(shí)現(xiàn)如下

/**
     * 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) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

這里的EMPTY_ELEMENTDATA 實(shí)際上就是一個(gè)共享的空的Object數(shù)組對(duì)象快集。

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

上述代碼很容易理解贡羔,如果用戶指定的初始化容量大于0,就new一個(gè)相應(yīng)大小的數(shù)組个初,如果指定的大小為0乖寒,就復(fù)制為共享的那個(gè)空的Object數(shù)組對(duì)象。如果小于0院溺,就直接拋出異常楣嘁。

  • public ArrayList() 默認(rèn)的空的構(gòu)造函數(shù)。
    我們一般會(huì)這么使用
 myList = new ArrayList();

源碼中的實(shí)現(xiàn)是

/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

其中DEFAULTCAPACITY_EMPTY_ELEMENTDATA 定義為

/**
     * 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 = {};

注釋中解釋的很清楚覆获,就是說(shuō)剛初始化的時(shí)候马澈,會(huì)是一個(gè)共享的類(lèi)變量,也就是一個(gè)Object空數(shù)組弄息,當(dāng)?shù)谝淮蝍dd的時(shí)候痊班,這個(gè)數(shù)組就會(huì)被初始化一個(gè)大小為10的數(shù)組。

  • public ArrayList(Collection<? extends E> c) 如果我們想要初始化一個(gè)list摹量,這個(gè)list包含另外一個(gè)特定的collection的元素涤伐,那么我們就可以調(diào)用這個(gè)構(gòu)造函數(shù)。
    我們通常會(huì)這么使用
Set<Integer> set = new HashSet<>();
        set.add(1);
        set.add(2);
        set.add(3);
        set.add(4);
ArrayList<Integer> list = new ArrayList<>(set);

源碼中是這么實(shí)現(xiàn)的

/**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

首先調(diào)用給定的collection的toArray方法將其轉(zhuǎn)換成一個(gè)Array缨称。
然后根據(jù)這個(gè)array的大小進(jìn)行判斷凝果,如果不為0,就調(diào)用Arrays的copyOf的方法睦尽,復(fù)制到Object數(shù)組中器净,完成初始化,如果為0当凡,就直接初始化為空的Object數(shù)組山害。

ArrayList是如何動(dòng)態(tài)增長(zhǎng)

當(dāng)我們像一個(gè)ArrayList中添加數(shù)組的時(shí)候,首先會(huì)先檢查數(shù)組中是不是有足夠的空間來(lái)存儲(chǔ)這個(gè)新添加的元素沿量。如果有的話浪慌,那就什么都不用做,直接添加朴则。如果空間不夠用了权纤,那么就根據(jù)原始的容量增加原始容量的一半。
源碼中是如此實(shí)現(xiàn)的:

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

ensureCapacityInternal的實(shí)現(xiàn)如下:

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

DEFAULT_CAPACITY為:

private static final int DEFAULT_CAPACITY = 10;

這也就實(shí)現(xiàn)了當(dāng)我們不指定初始化大小的時(shí)候,添加第一個(gè)元素的時(shí)候汹想,數(shù)組會(huì)擴(kuò)容為10.

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

這個(gè)函數(shù)判斷是否需要擴(kuò)容外邓,如果需要就調(diào)用grow方法擴(kuò)容

/**
     * 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
     */
    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:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

我們可以看到grow方法將數(shù)組擴(kuò)容為原數(shù)組的1.5倍,調(diào)用的是Arrays.copy
方法古掏。
在jdk6及之前的版本中坐榆,采用的還不是右移的方法

int newCapacity = (oldCapacity * 3)/2 + 1;

現(xiàn)在已經(jīng)優(yōu)化成右移了。

ArrayList如何實(shí)現(xiàn)元素的移除

我們移除元素的時(shí)候冗茸,有兩種方法席镀,一是指定下標(biāo),二是指定對(duì)象

list.remove(3);//index
list.remove("aaa");//object

下面先來(lái)分析第一種夏漱,也就是

  • public E remove(int index)
    源碼中是如此實(shí)現(xiàn)的
/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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] = null; // clear to let GC do its work

        return oldValue;
    }

對(duì)于數(shù)組的元素刪除算法我們應(yīng)該很熟悉豪诲,刪除一個(gè)數(shù)組元素,我們需要將這個(gè)元素后面的元素全部向前移動(dòng)挂绰,并將size減1.
我們看到源碼中屎篱,首先檢查下標(biāo)是否在可用范圍內(nèi)。然后調(diào)用System.arrayCopy方法將右邊的數(shù)組向左移動(dòng)葵蒂,并且將size減一交播,并置為null。

  • public boolean remove(Object o)
    源碼中實(shí)現(xiàn)如下:
/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    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;
    }

我們可以看到践付,這個(gè)remove方法會(huì)移除數(shù)組中第一個(gè)符合的給定對(duì)象秦士,如果不存在就什么也不做,如果存在多個(gè)只移除第一個(gè)永高。
fastRemove方法如下

/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    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;
    }

可以理解為簡(jiǎn)化版的remove(index)方法隧土。

ArrayList小結(jié)

  • ArrayList是List接口的一個(gè)可變大小的數(shù)組的實(shí)現(xiàn)

  • ArrayList的內(nèi)部是使用一個(gè)Object對(duì)象數(shù)組來(lái)存儲(chǔ)元素的

  • 初始化ArrayList的時(shí)候,可以指定初始化容量的大小命爬,如果不指定曹傀,就會(huì)使用默認(rèn)大小,為10

  • 當(dāng)添加一個(gè)新元素的時(shí)候饲宛,首先會(huì)檢查容量是否足夠添加這個(gè)元素皆愉,如果夠就直接添加,如果不夠就進(jìn)行擴(kuò)容艇抠,擴(kuò)容為原數(shù)組容量的1.5倍

  • 當(dāng)刪除一個(gè)元素的時(shí)候幕庐,會(huì)將數(shù)組右邊的元素全部左移

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市练链,隨后出現(xiàn)的幾起案子翔脱,更是在濱河造成了極大的恐慌奴拦,老刑警劉巖媒鼓,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡绿鸣,警方通過(guò)查閱死者的電腦和手機(jī)疚沐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)潮模,“玉大人亮蛔,你說(shuō)我怎么就攤上這事∏嫦幔” “怎么了究流?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)动遭。 經(jīng)常有香客問(wèn)我芬探,道長(zhǎng),這世上最難降的妖魔是什么厘惦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任偷仿,我火速辦了婚禮,結(jié)果婚禮上宵蕉,老公的妹妹穿的比我還像新娘酝静。我一直安慰自己,他們只是感情好羡玛,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布别智。 她就那樣靜靜地躺著,像睡著了一般稼稿。 火紅的嫁衣襯著肌膚如雪亿遂。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天渺杉,我揣著相機(jī)與錄音蛇数,去河邊找鬼。 笑死是越,一個(gè)胖子當(dāng)著我的面吹牛耳舅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播倚评,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼浦徊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了天梧?” 一聲冷哼從身側(cè)響起盔性,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎呢岗,沒(méi)想到半個(gè)月后冕香,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蛹尝,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年悉尾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了突那。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡构眯,死狀恐怖愕难,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情惫霸,我是刑警寧澤猫缭,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站壹店,受9級(jí)特大地震影響饵骨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茫打,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一居触、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧老赤,春花似錦轮洋、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至开财,卻和暖如春汉柒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背责鳍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工碾褂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人历葛。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓正塌,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親恤溶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子乓诽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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