Java基礎(chǔ)之ArrayList

ArrayList相信大家都用過撒妈,那么今天就來聊聊ArrayList缕棵。

概述

ArrayList是一個相對來說比較簡單的數(shù)據(jù)結(jié)構(gòu)瘩绒,底層是用數(shù)組實現(xiàn)的困肩,它和數(shù)組最大的區(qū)別就是可以自動擴(kuò)容,即我們可以認(rèn)為ArrayList就是一個動態(tài)數(shù)組拗窃。

ArrayList 允許空值和重復(fù)元素瞎领,ArrayList 是非線程安全類,并發(fā)環(huán)境下随夸,多個線程同時操作 ArrayList九默,會引發(fā)不可預(yù)知的錯誤。

源碼分析

構(gòu)造方法

    private static final Object[] EMPTY_ELEMENTDATA = {};

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //transient 修飾宾毒,不參與序列化
    transient Object[] elementData;
    
    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);
        }
    }

    
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

ArrayList 有兩種構(gòu)造方法驼修,一種有參,一種無參诈铛。邏輯都很簡單乙各,目的就是初始化底層的數(shù)組elementData。默認(rèn)的構(gòu)造方法會初始化一個空的數(shù)組癌瘾。

增加元素

    public boolean add(E e) {
        //檢查是否需要擴(kuò)容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //添加元素到末尾
        elementData[size++] = e;
        return true;
    }
    
    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方法有兩種觅丰,一種是直接添加元素到末尾,一種是添加到指定位置妨退。兩種步驟差不多妇萄,就是第二種會有一個將元素整體位移的操作。(第二種效率肯定較低咬荷,在大集合中應(yīng)該盡量避免調(diào)用)

從上面代碼中不難看出冠句,擴(kuò)容的操作是在ensureCapacityInternal方法中完成的,那么我們來看看擴(kuò)容的具體過程幸乒。



    private static final int DEFAULT_CAPACITY = 10;
    
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    //邊界檢查懦底,保證最小容量 >10 
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    //擴(kuò)容的核心方法
    private void grow(int minCapacity) {
    
        int oldCapacity = elementData.length;
        //擴(kuò)容為原來的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //仍然不夠就直接擴(kuò)容為需求值
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            // 如果最小容量超過 MAX_ARRAY_SIZE,則將數(shù)組容量擴(kuò)容至 Integer.MAX_VALUE
            newCapacity = hugeCapacity(minCapacity);
        
        //產(chǎn)生新的數(shù)組罕扎,并復(fù)制原有數(shù)組到新數(shù)組
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

代碼雖然不短聚唐,但是大部分操作都是邊界檢查。擴(kuò)容的核心方法是grow方法腔召。它計算出新的容器大懈瞬椤(即newCapacity),并確保了newCapacity不會比minCapacity小臀蛛,最后調(diào)用Arrays.copyOf()創(chuàng)建一個新的數(shù)組并將數(shù)據(jù)拷貝到新數(shù)組中亲桦,最后讓elementData進(jìn)行引用崖蜜。默認(rèn)的擴(kuò)容大小是當(dāng)前數(shù)組大小的一半,即擴(kuò)容至當(dāng)前容量的1.5倍客峭。

刪除元素

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

刪除操作也不是很復(fù)雜豫领,就直接將刪除元素后面的所有元素向前移一位,這樣目標(biāo)元素就會被覆蓋舔琅,然后把最后的那個元素置空等恐,同時將size變量減1。

    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
    }

remove方法還有另一種搏明,用要刪除的Object作為參數(shù)鼠锈。這種就是從前往后遍歷整個數(shù)組,找到第一個和目標(biāo)相等的對象星著,然后執(zhí)行刪除操作,刪除的過程和指定位置刪除沒有區(qū)別粗悯。如果刪除了元素會返回true虚循,該元素不存在則返回false。

查詢及修改

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

        return elementData(index);
    }

get方法样傍,直接返回目標(biāo)元素横缔。

    public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

set方法,直接修改目標(biāo)元素衫哥。

總結(jié)

ArrayList是我們常用的集合類型茎刚,底層由數(shù)組實現(xiàn),方法都非常簡單撤逢。在用無參構(gòu)造方法創(chuàng)建時會創(chuàng)建一個空數(shù)組膛锭,直到有元素放入后才會擴(kuò)容為10。默認(rèn)擴(kuò)容為原來的1.5倍蚊荣,如果1.5倍仍不夠初狰,則會直接擴(kuò)容到目標(biāo)值。由于擴(kuò)容操作會將原數(shù)組拷貝到創(chuàng)建出的新數(shù)組中互例,因此開銷較大奢入,應(yīng)盡量在初始化時設(shè)置好數(shù)組的容量。

由于底層為數(shù)組實現(xiàn)媳叨,隨機(jī)讀寫開銷小腥光,因此ArrayList適用于存儲訪問頻繁,但增刪較少的數(shù)據(jù)糊秆。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末武福,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子扩然,更是在濱河造成了極大的恐慌艘儒,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異界睁,居然都是意外死亡觉增,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門翻斟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逾礁,“玉大人,你說我怎么就攤上這事访惜∴诼模” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵债热,是天一觀的道長砾嫉。 經(jīng)常有香客問我,道長窒篱,這世上最難降的妖魔是什么焕刮? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮墙杯,結(jié)果婚禮上配并,老公的妹妹穿的比我還像新娘。我一直安慰自己高镐,他們只是感情好溉旋,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嫉髓,像睡著了一般观腊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岩喷,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天恕沫,我揣著相機(jī)與錄音,去河邊找鬼纱意。 笑死婶溯,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的偷霉。 我是一名探鬼主播迄委,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼类少!你這毒婦竟也來了叙身?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤硫狞,失蹤者是張志新(化名)和其女友劉穎信轿,沒想到半個月后晃痴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡财忽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年倘核,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片即彪。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡紧唱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出隶校,到底是詐尸還是另有隱情漏益,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布深胳,位于F島的核電站绰疤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏舞终。R本人自食惡果不足惜峦睡,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望权埠。 院中可真熱鬧,春花似錦煎谍、人聲如沸攘蔽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽满俗。三九已至,卻和暖如春作岖,著一層夾襖步出監(jiān)牢的瞬間唆垃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工痘儡, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留辕万,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓沉删,卻偏偏與公主長得像渐尿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子矾瑰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

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