ArrayList內(nèi)部原理解析

博文出處:ArrayList內(nèi)部原理解析,歡迎大家關(guān)注我的博客,謝謝!

注:本文解析的 ArrayList 源代碼基于 Java 1.8 。

Header

之前講了 HashMap 的原理后,今天來(lái)看一下 ArrayList 。

ArrayList 也是非常常用的集合類。它是有序的并且可以存儲(chǔ)重復(fù)元素的。 ArrayList 底層其實(shí)就是一個(gè)數(shù)組,并且會(huì)動(dòng)態(tài)擴(kuò)容的。

源碼分析

構(gòu)造方法

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        // 創(chuàng)建初始容量的數(shù)組
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

public ArrayList() {
    // 默認(rèn)為空數(shù)組
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        // 將集合中的元素復(fù)制到數(shù)組中
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

構(gòu)造方法中的代碼比較簡(jiǎn)短,大家都能理解的吧。

add()

    public boolean add(E e) {
        // 確保數(shù)組的容量,保證可以添加該元素
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 將該元素放入數(shù)組中
        elementData[size++] = e;
        return true;
    }

發(fā)現(xiàn)在 add() 方法中,代碼很簡(jiǎn)短??梢钥闯鲋暗念A(yù)操作都放入了 ensureCapacityInternal 方法中,這個(gè)方法會(huì)去確保該元素在數(shù)組中有位置可以放入。

那么我們來(lái)看看這個(gè)方法:

    private void ensureCapacityInternal(int minCapacity) {
        // 如果數(shù)組是空的,那么會(huì)初始化該數(shù)組
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // DEFAULT_CAPACITY 為 10 ,所以調(diào)用無(wú)參默認(rèn) ArrayList 構(gòu)造方法初始化的話,默認(rèn)的數(shù)組容量為 10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
    
        ensureExplicitCapacity(minCapacity);
    }

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

        // 確保數(shù)組的容量,如果不夠的話,調(diào)用 grow 方法擴(kuò)容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

看了半天,擴(kuò)容是在 grow 方法中完成的,所以我們接著跟進(jìn)。

    private void grow(int minCapacity) {
        // 當(dāng)前數(shù)組的容量
        int oldCapacity = elementData.length;
        // 新數(shù)組擴(kuò)容為原來(lái)容量的 1.5 倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果新數(shù)組擴(kuò)容容量還是比最少需要的容量還要小的話,就設(shè)置擴(kuò)充容量為最小需要的容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //判斷新數(shù)組容量是否已經(jīng)超出最大數(shù)組范圍,MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        // 復(fù)制元素到新的數(shù)組中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

擴(kuò)容方法其實(shí)就是新創(chuàng)建一個(gè)數(shù)組,然后將舊數(shù)組的元素都復(fù)制到新數(shù)組里面。

當(dāng)然,add 還有一個(gè)重載的方法 add(int index, E element) ,順便我們也來(lái)看一下。

    public void add(int index, E element) {
        // 判斷 index 有沒有超出索引的范圍
        rangeCheckForAdd(index);
        // 和之前的操作是一樣的,都是保證數(shù)組的容量足夠
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 將指定位置及其后面數(shù)據(jù)向后移動(dòng)一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // 將該元素添加到指定的數(shù)組位置
        elementData[index] = element;
        // ArrayList 的大小改變
        size++;
    }

好了,add 方法看的差不多了,剩下還有一個(gè) addAll(Collection<? extends E> c) 方法也是換湯不換藥的,可以自己回去看下,這里就不講了。

get()

get 方法很簡(jiǎn)單,就是在數(shù)組中返回指定位置的元素即可。

    public E get(int index) {
        // 檢查 index 有沒有超出索引的范圍
        rangeCheck(index);
        // 返回指定位置的元素
        return elementData(index);
    }

remove()

    public E remove(int index) {
        // 檢查 index 有沒有超出索引的范圍
        rangeCheck(index);

        modCount++;
        // 保存一下需要?jiǎng)h除的數(shù)據(jù)
        E oldValue = elementData(index);
        // 讓指定刪除的位置后面的數(shù)據(jù),向前移動(dòng)一位
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 方便 gc 釋放內(nèi)存
        elementData[--size] = null; // clear to let GC do its work
        // 返回舊值
        return oldValue;
    }

remove 中主要是將之后的元素都向前一位移動(dòng),然后將最后一位的值設(shè)置為空。最后,返回已經(jīng)刪除的值。

同樣,remove 還有一個(gè)重載的方法 remove(Object o)

    public boolean remove(Object o) {
        if (o == null) {
            // 如果有元素的值為 null 的話,移除該元素,fastRemove 的操作和上面的 remove(int index) 是類似的
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            // 如果有元素的值等于 o 的話,移除該元素
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

clear()

clear 方法無(wú)非就是遍歷數(shù)組,然后把所有的值都置為 null 。

    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

Footer

至此,ArrayList 主要的幾個(gè)方法就講完了。ArrayList 的源碼還是比較簡(jiǎn)單的,基本上都可以看得明白。

我們來(lái)總結(jié)一下:

  1. ArrayList底層是基于數(shù)組來(lái)實(shí)現(xiàn)的,因此在 get 的時(shí)候效率高,而 add 或者 remove 的時(shí)候,效率低;
  2. 調(diào)用默認(rèn)的 ArrayList 無(wú)參構(gòu)造方法的話,數(shù)組的初始容量為 10 ;
  3. ArrayList 會(huì)自動(dòng)擴(kuò)容,擴(kuò)容的時(shí)候,會(huì)將容量擴(kuò)至原來(lái)的 1.5 倍;
  4. ArrayList 不是線程安全的;

那么今天就這樣了,之后有空給大家講講 LinkedList 。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • java筆記第一天 == 和 equals ==比較的比較的是兩個(gè)變量的值是否相等,對(duì)于引用型變量表示的是兩個(gè)變量...
    jmychou閱讀 1,648評(píng)論 0 3
  • 一.線性表 定義:零個(gè)或者多個(gè)元素的有限序列。也就是說它得滿足以下幾個(gè)條件:??①該序列的數(shù)據(jù)元素是有限的。??②...
    Geeks_Liu閱讀 2,768評(píng)論 1 12
  • 文章有點(diǎn)長(zhǎng),比較啰嗦,請(qǐng)耐心看完?。ɑ贏ndroid API 25) 一、概述 首先得明白ArrayList在數(shù)...
    JerryloveEmily閱讀 3,380評(píng)論 2 15
  • 最近在惡補(bǔ)數(shù)據(jù)結(jié)構(gòu)和算法,順帶著把ArrayList和Vector看了看,因此寫了這篇文章記錄下。我們知道Arra...
    Zeit丶閱讀 2,561評(píng)論 0 4
  • 呃 其實(shí)是昨天追太陽(yáng)的后裔和銀魂搞得第二天的內(nèi)容沒寫完。最近好虐 國(guó)足出線 銀魂本季完結(jié) 心塞...不過每天洗完澡...
    QJK閱讀 423評(píng)論 0 0

友情鏈接更多精彩內(nèi)容