Java集合 - ArrayList

ArrayList

ArrayList集合是我們平時使用相當多的集合了际长,本文是我學習ArrayList的源碼铃剔,對于ArrayList源碼相關方法實現(xiàn)的記錄费变。

ArrayList繼承結構

ArrayList繼承結構

ArrayList初始化

其實ArrayList底層就是一個數(shù)組粥帚。

private transient Object[] elementData;

對這個數(shù)組(也就是ArrayList)的初始化一共有三種方式,分別如下祟剔。

/**
* 第一種方式傅事,設定初始大小。
**/
public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

/**
* 第二種方式峡扩,初始化為空的數(shù)組。
**/
private static final Object[] EMPTY_ELEMENTDATA = {};

public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }

/**
* 第三種方式障本,使用現(xiàn)有的集合教届,對ArrayList進行初始化
**/
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

ArrayList底層原理

ArrayList底層就是一個數(shù)組响鹃,所有add進去的對象都會存儲在elementData這個數(shù)組中。

private transient Object[] elementData;        //存儲對象的數(shù)組

首先先來看add方法案训,這里有重載的add方法允許我們分別在index位置和集合最后一個對象后面添加對象买置。

在調(diào)用add方法添加對象的時候,會先調(diào)用ensureCapacityInternal方法强霎,該方法會判斷elementData是否是空的數(shù)組忿项,如果是的話,那么將會把最小容量設置為10城舞,否則最小容量為size + 1轩触。之后調(diào)用ensureExplicitCapacity方法,如果最小容量大于elementData的長度家夺,那么代表數(shù)組存不下了脱柱,那么則進行grow方法進行數(shù)組的擴容操作。

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //插入新元素拉馋,判斷size+1是否超過了數(shù)組的大小榨为,如果超過了則進行擴容
        elementData[size++] = e;
        return true;
    }

private static final int DEFAULT_CAPACITY = 10;    //集合的默認長度

private void ensureCapacityInternal(int minCapacity) {
        //判空操作,如果elementData是{}的話煌茴,則minCapacity將變成默認長度10随闺。
        if (elementData == EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

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

        //如果size +1大于elementData的長度,那么就代表數(shù)組不夠存了蔓腐,就要調(diào)用grow對數(shù)組進行擴容矩乐。
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

//elementData擴容方法
private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);  //新的容量為舊容量的1.5倍。
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);    //將舊數(shù)組中的元素拷貝到新容量的新數(shù)組中
    }

/**
* 在集合指定位置插入對象合住,與上面的add方法原理相同绰精。
**/
public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

add方法總結:調(diào)用方法時,則判斷size+1是否還小于elementData的長度透葛,也就是數(shù)組是否還能存的下笨使,如果存不下,則將數(shù)組擴容為原來的1.5倍長度僚害,將舊數(shù)組中的所有對象拷貝到新長度的數(shù)組中硫椰,從而先實現(xiàn)了ArrayList可以無限存入對象。

toArray方法

public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

public <T> T[] toArray(T[] a) {          //普遍使用這個
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

get方法

public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

private void rangeCheck(int index) {          //判斷是否會發(fā)生數(shù)組越界
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

E elementData(int index) {
        return (E) elementData[index];
    }

檢查index是否數(shù)組越界了萨蚕,之后返回對應位置的對象靶草。

remove方法(容易遇坑的方法)

這里容易遇到兩個坑

坑一,使用for循環(huán)時刪除元素岳遥。
下面代碼是為了刪除集合中2的倍數(shù)的元素奕翔。

public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(2);
        arrayList.add(4);
        arrayList.add(5);
        arrayList.add(4);
        arrayList.add(0);
        arrayList.add(1);
        arrayList.add(8);

        for(int i = 0; i < arrayList.size(); i++){
            if((arrayList.get(i) % 2) == 0){
                arrayList.remove(i);
            }
        }

        System.out.println(Arrays.toString(arrayList.toArray()));
    }

最后運行結果為 [4, 5, 0, 1]
那么問題來了,4也是2的倍數(shù)浩蓉,怎么沒被刪除派继?

觀察remove的代碼

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);    //刪除index位置的元素宾袜,所有元素向前移動。
        elementData[--size] = null; 

        return oldValue;
    }

這里使用 System.arraycopy()方法驾窟,將index位置后的元素向前移動庆猫。那么此時i指向的位置還是index的位置,但是i+1指向的卻是刪除之前index+2的位置了绅络,也就是說下一次循環(huán)月培,就會跳過一個元素。所以每一次刪除之后要將i--恩急。

坑二杉畜,使用foreach循環(huán)的時候進行remove操作。

public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(2);
        arrayList.add(4);
        arrayList.add(5);
        arrayList.add(4);
        arrayList.add(0);
        arrayList.add(1);
        arrayList.add(8);

        for(Integer integer : arrayList){
            if((integer % 2) == 0){
                arrayList.remove(integer);
            }
        }

        System.out.println(Arrays.toString(arrayList.toArray()));
    }

這是會報ConcurrentModificationException異常
觀察源碼

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;
    }

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
    }

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];
        }

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

原因:foreach使用的是迭代器假栓,每一次remove寻行,modCount都會+1,之后iterator調(diào)用next方法匾荆,會執(zhí)行checkForComodification方法拌蜘, 就會發(fā)現(xiàn)modCount和expectedModCount值不同就會出現(xiàn)異常。
解決辦法牙丽,獲得迭代器對象简卧,使用迭代器的remove方法。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末烤芦,一起剝皮案震驚了整個濱河市举娩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌构罗,老刑警劉巖铜涉,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異遂唧,居然都是意外死亡芙代,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門盖彭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纹烹,“玉大人,你說我怎么就攤上這事召边∑毯牵” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵隧熙,是天一觀的道長片挂。 經(jīng)常有香客問我,道長贞盯,這世上最難降的妖魔是什么宴卖? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任滋将,我火速辦了婚禮,結果婚禮上症昏,老公的妹妹穿的比我還像新娘。我一直安慰自己父丰,他們只是感情好肝谭,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蛾扇,像睡著了一般攘烛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上镀首,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天坟漱,我揣著相機與錄音,去河邊找鬼更哄。 笑死芋齿,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的成翩。 我是一名探鬼主播觅捆,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼麻敌!你這毒婦竟也來了栅炒?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤术羔,失蹤者是張志新(化名)和其女友劉穎赢赊,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體级历,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡释移,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鱼喉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秀鞭。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖扛禽,靈堂內(nèi)的尸體忽然破棺而出锋边,到底是詐尸還是另有隱情,我是刑警寧澤编曼,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布矮冬,位于F島的核電站,受9級特大地震影響眠冈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贩猎,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望萍膛。 院中可真熱鬧吭服,春花似錦、人聲如沸蝗罗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽串塑。三九已至沼琉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間桩匪,已是汗流浹背打瘪。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留傻昙,地道東北人闺骚。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像屋匕,于是被迫代替她去往敵國和親葛碧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354