ArrayList源碼分析

ArrayList是Java中常用的一個集合類畅姊,是List接口的一個實現(xiàn)類席怪,而List接口繼承自Collection接口腌紧,所以ArrayList是Collection的一個實現(xiàn)類砸喻。

本篇主要討論一下ArrayList底層代碼的實現(xiàn)陶珠。

核心的成員變量

先來看看ArrayList中幾個核心的成員變量挟裂。

    //初始化數(shù)組的大小,ArrayList底層是基于數(shù)組實現(xiàn)的
    private static final int DEFAULT_CAPACITY = 10;
    //一個空數(shù)組對象动分,用于無參數(shù)的情況下初始化數(shù)組
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //底層存放數(shù)據(jù)的數(shù)組
    transient Object[] elementData; 
    //已存放的元素數(shù)量
    private int size;
    //定義數(shù)組的最大容量滨溉,實際數(shù)組的大小可以擴大到Integer.MAX_VALUE的大小住诸,該字段應(yīng)該是預(yù)留字段
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

默認(rèn)無參的構(gòu)造函數(shù)

    public ArrayList() {
        //這里首先初始化用于存放數(shù)據(jù)的數(shù)組,默認(rèn)數(shù)組的大小為空
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

add方法實現(xiàn)

    //這里添加一個元素渠啤,添加成功后將size+1,并返回true
    public boolean add(E e) {
        //傳入size+1添吗,表示數(shù)組的容量應(yīng)該滿足最小的容量
        ensureCapacityInternal(size + 1);
        //將當(dāng)前添加的元素存放到數(shù)組中
        elementData[size++] = e;
        return true;
    }
    
    //該方法用來確保當(dāng)前數(shù)組的大小是否滿足最小要求沥曹,并對數(shù)組擴容
    private void ensureCapacityInternal(int minCapacity) {
        //calculateCapacity方法傳入存放數(shù)據(jù)的數(shù)組與該數(shù)組要求滿足的最小容量,并返回設(shè)置的容量大小
        //ensureExplicitCapacity會判斷當(dāng)前數(shù)組的是否需要擴容
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //當(dāng)數(shù)組的第一次使用時碟联,返回初始化容量DEFAULT_CAPACITY=10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

   //判斷當(dāng)前數(shù)組的是否需要擴容
   private void ensureExplicitCapacity(int minCapacity) {
        //該變量為AbstractList中的成員變量妓美,在用iterator迭代器獲取數(shù)據(jù)的數(shù)據(jù)使用
        modCount++;
        //此處判斷當(dāng)前數(shù)組的容量是否滿足要求
        if (minCapacity - elementData.length > 0)
            //調(diào)用該方法對數(shù)組進(jìn)行擴容
            grow(minCapacity);
    }

    //實際進(jìn)行數(shù)組擴容拷貝操作
    private void grow(int minCapacity) {
        //當(dāng)前數(shù)組的容量大小
        int oldCapacity = elementData.length;
        //新數(shù)組的容量大小,oldCapacity >> 1實際為oldCapacity/2
        //這里也就是將數(shù)組的容量擴大1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //由于第一次調(diào)用該方法時鲤孵,數(shù)組的容量大小為0壶栋,所以這里會將默認(rèn)數(shù)組大小賦予newCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //當(dāng)新數(shù)組的容量大小大于MAX_ARRAY_SIZE時,將數(shù)組的大小設(shè)置為Integer.MAX_VALUE
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);

        //最后普监,將數(shù)組進(jìn)行拷貝擴容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    //該方法返回數(shù)組的最大容量大小
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

具體步驟如下:

  • 當(dāng)?shù)谝淮握{(diào)用add方法添加元素時贵试,會對elementData數(shù)組進(jìn)行擴容,默認(rèn)大小為DEFAULT_CAPACITY=10鹰椒;
  • 之后每一次調(diào)用add方法時锡移,會對elementData數(shù)組的大小進(jìn)行判斷,判斷當(dāng)前容量是否滿足最小要求漆际;
  • 如果不滿足淆珊,則調(diào)用grow方法,對數(shù)組的大小擴容為原來大小的1.5倍奸汇;
  • 當(dāng)elementData數(shù)組的容量大小大于MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)時施符,將數(shù)組的容量大小設(shè)置為Integer.MAX_VALUE往声;
  • 添加成功后,對size加1戳吝,并對數(shù)組進(jìn)行賦值數(shù)據(jù)浩销,然后返回true;

帶參數(shù)的構(gòu)造函數(shù)

  //傳入初始化數(shù)組大小的參數(shù)
  public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //初始化elementData數(shù)組大小
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

在add方法中听哭,我們了解到慢洋,在添加元素的過程中,涉及到對元素進(jìn)行擴容的操作陆盘,如果需要在數(shù)組中頻繁添加大量元素普筹,將會對elementData數(shù)組進(jìn)行多次拷貝擴容操作,很消耗性能隘马,所以太防,我們可以先預(yù)估數(shù)組的大小,并調(diào)用帶參數(shù)的構(gòu)造函數(shù)酸员,傳入初始化數(shù)組的大小的參數(shù)蜒车,可以避免在add元素的拷貝擴容操作,提升性能幔嗦。

get方法實現(xiàn)

   //傳入要獲取數(shù)據(jù)的索引下標(biāo)酿愧,返回對應(yīng)位置的數(shù)據(jù)
   public E get(int index) {
        //檢查該索引下標(biāo)是否越界
        rangeCheck(index);

        return elementData(index);
    }

    //判斷當(dāng)前索引下標(biāo)是否大于當(dāng)前數(shù)組存放數(shù)據(jù)的大小,如果索引下標(biāo)越界崭添,則報IndexOutOfBoundsException異常
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
   
    //返回數(shù)組中對應(yīng)索引下標(biāo)位置的數(shù)據(jù)
    E elementData(int index) {
        return (E) elementData[index];
    }

get方法實現(xiàn)比較簡單寓娩,步驟如下:

  • 首先對傳入索引下標(biāo)進(jìn)行檢查,如果下標(biāo)越界呼渣,則拋出IndexOutOfBoundsException異常棘伴;
  • 如果該索引下標(biāo)合法,則返回對應(yīng)索引下標(biāo)位置的數(shù)據(jù)屁置;

remove(int index)方法實現(xiàn)

   //傳入要刪除的索引下標(biāo)焊夸,并返回該索引下標(biāo)元素,也就是刪除了的元素
   public E remove(int index) {
        //檢查索引下標(biāo)是否合法
        rangeCheck(index);

        modCount++;
        //取出該下標(biāo)位置的元素
        E oldValue = elementData(index);

        //數(shù)組中要向前移動的元素的數(shù)量蓝角,刪除指定索引元素時阱穗,會將該索引后面的元素向前移
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //將指定索引下標(biāo)之后的元素向前移動
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);

        //因為數(shù)組向前移動了,所以將數(shù)組中最后一個元素的值設(shè)置為null
        elementData[--size] = null; 

        //返回刪除了的元素
        return oldValue;
    }

具體步驟如下:

  • 檢查指定索引下標(biāo)是否合法使鹅,如不合法揪阶,拋出IndexOutOfBoundsException異常;
  • 獲取指定索引下標(biāo)的元素數(shù)據(jù)患朱,用于刪除后返回鲁僚;
  • 刪除數(shù)據(jù)是基于數(shù)組前移操作的,numMoved 為要前移元素的數(shù)量,假如當(dāng)前數(shù)組為[1,2,3,4]冰沙,那么數(shù)組的size為4侨艾,當(dāng)要刪除的index=1時,這里計算出來的numMoved=2拓挥,所以實際調(diào)用的是System.arraycopy([1,2,3,4], 2, [1,2,3,4], 1, 2)唠梨,也就是從索引下標(biāo)為2的位置開始后兩個元素向前移動到下標(biāo)為1的位置,移動后的結(jié)果為[1,3,4,4]侥啤;
  • 將數(shù)組元素的最后一個值設(shè)置為null当叭;
  • 返回刪除了的元素;

remove(Object o)方法實現(xiàn)

  //刪除某個元素愿棋,刪除成功科展,返回true,否則返回false
  public boolean remove(Object o) {
       //如果要刪除的對象為null糠雨,則刪除數(shù)組第一個為null的元素
        if (o == null) {
            //遍歷數(shù)組,獲取出要刪除的元素的下標(biāo)
            for (int index = 0; index < size; index++)
                //查找出第一個元素為null的下標(biāo)
                if (elementData[index] == null) {
                    //執(zhí)行按照索引刪除元素徘跪,實現(xiàn)方法與remove(int index)類似
                    fastRemove(index);
                    return true;
                }
        } else {

            //遍歷數(shù)組甘邀,獲取出要刪除的元素的下標(biāo)
            for (int index = 0; index < size; index++)
                //查找出要刪除的元素的下標(biāo)
                if (o.equals(elementData[index])) {
                     //執(zhí)行按照索引刪除元素,實現(xiàn)方法與remove(int index)類似
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
 
   //刪除指定索引的元素垮庐,與remove(int index)方法邏輯類似
   private void fastRemove(int index) {
        modCount++;
        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
    }

具體步驟:

  • 判斷要刪除的元素是否為null松邪,如果為null,則先遍歷獲取數(shù)組中第一個為null的索引下標(biāo)哨查,然后根據(jù)該索引下標(biāo)刪除元素逗抑,也就是刪除數(shù)組中第一個為null的元素;
  • 如果要刪除的元素不為空寒亥,則遍歷獲取第一個出現(xiàn)在數(shù)組中的索引下標(biāo)邮府,然后根據(jù)索引下標(biāo)刪除該元素,也就是刪除第一個與元素匹配的數(shù)據(jù)溉奕;
  • 刪除成功褂傀,返回true,否則加勤,返回false;

適用場景

通過源碼的分析仙辟,我們知道,arrayList底層是基于數(shù)組實現(xiàn)的鳄梅,每個存儲的元素叠国,都擁有一個元素的下標(biāo),所以戴尸,當(dāng)我們需要通過下標(biāo)獲取數(shù)據(jù)的時候粟焊,適合使用ArrayList來存取數(shù)據(jù);而當(dāng)在添加與刪除元素的時候,可能會涉及到數(shù)組的擴容與拷貝吆玖,所以arrayList不適添加和刪除比較多的場景筒溃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市沾乘,隨后出現(xiàn)的幾起案子怜奖,更是在濱河造成了極大的恐慌,老刑警劉巖翅阵,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歪玲,死亡現(xiàn)場離奇詭異,居然都是意外死亡掷匠,警方通過查閱死者的電腦和手機滥崩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讹语,“玉大人钙皮,你說我怎么就攤上這事⊥缇觯” “怎么了短条?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長才菠。 經(jīng)常有香客問我茸时,道長,這世上最難降的妖魔是什么赋访? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任可都,我火速辦了婚禮,結(jié)果婚禮上蚓耽,老公的妹妹穿的比我還像新娘渠牲。我一直安慰自己,他們只是感情好田晚,可當(dāng)我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布嘱兼。 她就那樣靜靜地躺著,像睡著了一般贤徒。 火紅的嫁衣襯著肌膚如雪芹壕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天接奈,我揣著相機與錄音踢涌,去河邊找鬼。 笑死序宦,一個胖子當(dāng)著我的面吹牛睁壁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼潘明,長吁一口氣:“原來是場噩夢啊……” “哼行剂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钳降,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤厚宰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后遂填,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铲觉,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年吓坚,在試婚紗的時候發(fā)現(xiàn)自己被綠了撵幽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡礁击,死狀恐怖盐杂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情客税,我是刑警寧澤况褪,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站更耻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捏膨。R本人自食惡果不足惜秧均,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望号涯。 院中可真熱鬧目胡,春花似錦、人聲如沸链快。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽域蜗。三九已至巨双,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間霉祸,已是汗流浹背筑累。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留丝蹭,地道東北人慢宗。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親镜沽。 傳聞我的和親對象是個殘疾皇子敏晤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,494評論 2 348

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