ArrayList擴容機制

一秧秉、先從 ArrayList 的構(gòu)造函數(shù)說起

ArrayList有三種方式來初始化赖钞,構(gòu)造方法源碼如下:

/**
     * 默認初始容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;


    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     *默認構(gòu)造函數(shù),使用初始容量10構(gòu)造一個空列表(無參數(shù)構(gòu)造)
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 帶初始容量參數(shù)的構(gòu)造函數(shù)妒茬。(用戶自己指定容量)
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {//初始容量大于0
            //創(chuàng)建initialCapacity大小的數(shù)組
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {//初始容量等于0
            //創(chuàng)建空數(shù)組
            this.elementData = EMPTY_ELEMENTDATA;
        } else {//初始容量小于0仰美,拋出異常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }


   /**
    *構(gòu)造包含指定collection元素的列表迷殿,這些元素利用該集合的迭代器按順序返回
    *如果指定的集合為null,throws NullPointerException咖杂。
    */
     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;
        }
    }

細心的同學(xué)一定會發(fā)現(xiàn) :以無參數(shù)構(gòu)造方法創(chuàng)建 ArrayList 時庆寺,實際上初始化賦值的是一個空數(shù)組。當真正對數(shù)組進行添加元素操作時诉字,才真正分配容量懦尝。即向數(shù)組中添加第一個元素時知纷,數(shù)組容量擴為10。 下面在我們分析 ArrayList 擴容時會講到這一點內(nèi)容陵霉!

二琅轧、一步一步分析 ArrayList 擴容機制

這里以無參構(gòu)造函數(shù)創(chuàng)建的 ArrayList 為例分析:

1、先來看 add 方法

/**
     * 將指定的元素追加到此列表的末尾踊挠。
     */
    public boolean add(E e) {
      //添加元素之前乍桂,先調(diào)用ensureCapacityInternal方法
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //這里看到ArrayList添加元素的實質(zhì)就相當于為數(shù)組賦值
        elementData[size++] = e;
        return true;
    }

2、再來看看 ensureCapacityInternal() 方法

可以看到 add 方法 首先調(diào)用了ensureCapacityInternal(size + 1)

    //得到最小擴容量
    private void ensureCapacityInternal(int minCapacity) {
      // 判斷是否為一個空數(shù)組
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              // 獲取默認的容量和傳入?yún)?shù)的較大值 初始為10止毕,或傳入值的
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

當 要 add 進第1個元素時,minCapacity為1漠趁,在Math.max()方法比較后扁凛,minCapacity 為10。

3闯传、ensureExplicitCapacity() 方法

如果調(diào)用 ensureCapacityInternal() 方法就一定會進過(執(zhí)行)這個方法谨朝,下面我們來研究一下這個方法的源碼!

//判斷是否需要擴容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            //調(diào)用grow方法進行擴容甥绿,調(diào)用此方法代表已經(jīng)開始擴容了
            grow(minCapacity);
    }

我們來仔細分析一下:

1.當我們要 add 進第1個元素到 ArrayList 時字币,elementData.length 為0 (因為還是一個空的 list),因為執(zhí)行了 ensureCapacityInternal() 方法 共缕,所以 minCapacity 此時為10洗出。此時,minCapacity - elementData.length > 0 成立图谷,所以會進入 grow(minCapacity) 方法翩活。

2.當add第2個元素時,minCapacity 為2便贵,此時e lementData.length(容量)在添加第一個元素后擴容成 10 了菠镇。此時,minCapacity - elementData.length > 0 不成立承璃,所以不會進入 (執(zhí)行)grow(minCapacity) 方法利耍。

3.添加第3、4···到第10個元素時盔粹,依然不會執(zhí)行g(shù)row方法隘梨,數(shù)組容量都為10。

4.直到添加第11個元素舷嗡,minCapacity(為11)比elementData.length(為10)要大出嘹。進入grow方法進行擴容。

4咬崔、grow() 方法

/**
     * 要分配的最大數(shù)組大小
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * ArrayList擴容的核心方法税稼。
     */
    private void grow(int minCapacity) {
        // oldCapacity為舊容量烦秩,newCapacity為新容量
        int oldCapacity = elementData.length;
        //將oldCapacity 右移一位,其效果相當于oldCapacity /2郎仆,
        //我們知道位運算的速度遠遠快于整除運算只祠,整句運算式的結(jié)果就是將新容量更新為舊容量的1.5倍,
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //然后檢查新容量是否大于最小需要容量扰肌,若還是小于最小需要容量抛寝,那么就把最小需要容量當作數(shù)組的新容量,
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
       // 如果新容量大于 MAX_ARRAY_SIZE,進入(執(zhí)行) `hugeCapacity()` 方法來比較 minCapacity 和 MAX_ARRAY_SIZE曙旭,
       //如果minCapacity大于最大容量盗舰,則新容量則為`Integer.MAX_VALUE`,否則桂躏,新容量大小則為 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:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次擴容之后容量都會變?yōu)樵瓉淼?1.5 倍左右(oldCapacity為偶數(shù)就是1.5倍,否則是1.5倍左右)剂习! 奇偶不同蛮位,比如 :10+10/2 = 15, 33+33/2=49。如果是奇數(shù)的話會丟掉小數(shù).

我們再來通過例子探究一下grow() 方法 :

當add第1個元素時鳞绕,oldCapacity 為0失仁,經(jīng)比較后第一個if判斷成立,newCapacity = minCapacity(為10)们何。但是第二個if判斷不會成立萄焦,即newCapacity 不比 MAX_ARRAY_SIZE大,則不會進入 hugeCapacity 方法冤竹。數(shù)組容量為10楷扬,add方法中 return true,size增為1。

當add第11個元素進入grow方法時贴见,newCapacity為15烘苹,比minCapacity(為11)大,第一個if判斷不成立片部。新容量沒有大于數(shù)組最大size镣衡,不會進入hugeCapacity方法。數(shù)組容量擴為15档悠,add方法中return true,size增為11廊鸥。
以此類推······

這里補充一點比較重要,但是容易被忽視掉的知識點:

①java 中的 length 屬性是針對數(shù)組說的,比如說你聲明了一個數(shù)組,想知道這個數(shù)組的長度則用到了 length 這個屬性.

②java 中的 length() 方法是針對字符串說的,如果想看這個字符串的長度則用到 length() 這個方法.

③java 中的 size() 方法是針對泛型集合說的,如果想看這個泛型有多少個元素,就調(diào)用此方法來查看!

5辖所、hugeCapacity() 方法

從上面 grow() 方法源碼我們知道: 如果新容量大于 MAX_ARRAY_SIZE,進入(執(zhí)行) hugeCapacity() 方法來比較 minCapacity 和 MAX_ARRAY_SIZE惰说,如果minCapacity大于最大容量,則新容量則為Integer.MAX_VALUE缘回,否則吆视,新容量大小則為 MAX_ARRAY_SIZE 即為 Integer.MAX_VALUE - 8典挑。

private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //對minCapacity和MAX_ARRAY_SIZE進行比較
        //若minCapacity大,將Integer.MAX_VALUE作為新數(shù)組的大小
        //若MAX_ARRAY_SIZE大啦吧,將MAX_ARRAY_SIZE作為新數(shù)組的大小
        //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末您觉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子授滓,更是在濱河造成了極大的恐慌琳水,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件般堆,死亡現(xiàn)場離奇詭異在孝,居然都是意外死亡,警方通過查閱死者的電腦和手機淮摔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門私沮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人噩咪,你說我怎么就攤上這事顾彰〖模” “怎么了胃碾?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長筋搏。 經(jīng)常有香客問我仆百,道長,這世上最難降的妖魔是什么奔脐? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任俄周,我火速辦了婚禮,結(jié)果婚禮上髓迎,老公的妹妹穿的比我還像新娘峦朗。我一直安慰自己,他們只是感情好排龄,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布波势。 她就那樣靜靜地躺著,像睡著了一般橄维。 火紅的嫁衣襯著肌膚如雪尺铣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天争舞,我揣著相機與錄音凛忿,去河邊找鬼。 笑死竞川,一個胖子當著我的面吹牛店溢,可吹牛的內(nèi)容都是我干的叁熔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼逞怨,長吁一口氣:“原來是場噩夢啊……” “哼者疤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起叠赦,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤驹马,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后除秀,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體糯累,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年册踩,在試婚紗的時候發(fā)現(xiàn)自己被綠了泳姐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡暂吉,死狀恐怖胖秒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情慕的,我是刑警寧澤阎肝,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站肮街,受9級特大地震影響风题,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嫉父,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一沛硅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绕辖,春花似錦摇肌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至弟头,卻和暖如春吩抓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赴恨。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工疹娶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伦连。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓雨饺,卻偏偏與公主長得像钳垮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子额港,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348