集合框架 (第 03 篇) 源碼分析:ArrayList

一木西、集合框架源碼分析

原文持續(xù)更新鏈接: https://github.com/about-cloud/JavaCore

正文


本文關(guān)注點

★★★★★本文重點:ArrayList自動擴(kuò)容機(jī)制(1.5倍或1.5倍-1 擴(kuò)容)


一关翎、ArrayList

ArrayList是動態(tài)數(shù)組,其動態(tài)性體現(xiàn)在能夠動態(tài)擴(kuò)容 鸠信、動態(tài)縮容纵寝。(其原理是構(gòu)建一個新Object數(shù)組,將原數(shù)組復(fù)制進(jìn)去。借助 Arrays.copyOf(T[] original, int newLength)) 爽茴。

ArrayList繼承關(guān)系

重要的屬性

// 默認(rèn)初始容量為 10(當(dāng)ArrayList的容量低于9時才會用到)
private static final int DEFAULT_CAPACITY = 10;
// 存放數(shù)據(jù)的數(shù)組葬凳,數(shù)組的每個位置并不一定都填充數(shù)據(jù),用transient修飾避免序列化室奏、避免浪費資源
transient Object[] elementData;
// 記錄實際存放的元素個數(shù)
private int size;
// 記錄著ArrayList的修改次數(shù),也就每次add或remove火焰,它的值都會加1
protected transient int modCount = 0;

1.1、一般添加元素的方法 public boolean add(E e)

public boolean add(E e) {
    // modCount++胧沫,并且校驗容量昌简,不夠用就擴(kuò)容
    ensureCapacityInternal(size + 1);  // 增加 modCount
    // 將新元素添加到新數(shù)組size位置,并將size加1(千萬不要理解成將新元素添加到size+1的位置)
    elementData[size++] = e;
    return true;
}

// 確保容量夠用
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // 下標(biāo)溢出表示容量不夠用
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

本文重點方法★★★★★ ArrayList 擴(kuò)容方法 grow(int minCapacity)

// 擴(kuò)容方法
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // ArrayList 的擴(kuò)容是 1.5 倍 或 1.5 倍 - 1
    // oldCapacity為偶數(shù)按1.5倍擴(kuò)容绒怨,oldCapacity為奇數(shù)按1.5倍-1擴(kuò)容
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 該拷貝為深拷貝纯赎,將原來的數(shù)組復(fù)制到新的數(shù)組并返回新的數(shù)組
    elementData = java.util.Arrays.copyOf(elementData, newCapacity);
}

雖然上面的代碼不多,但是添加元素遇到經(jīng)常擴(kuò)容的現(xiàn)象要格外留心南蹂,因為頻繁的擴(kuò)容勢必帶來頻繁的數(shù)組拷貝犬金,這會大大犧牲性能,所以有必要在 ArrayList 初始化時指定容量六剥。初始化的容量也不要太大于實際存儲容量晚顷,不然會造成空間浪費,所以都要權(quán)衡利弊疗疟。

1.2音同、在指定位子添加方法 public void add(int index, E element)

// 注意:在指定 index 位置添加元素,并不會覆蓋該處的元素秃嗜,而是 index位置及其之后的元素后移
public void add(int index, E element) {
    // 只要有index,必定會檢查range
    rangeCheckForAdd(index);
    // 確保容量夠用(否則就擴(kuò)容)
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 這里是在指定位置index添加元素的關(guān)鍵:從index處顿膨,將數(shù)據(jù)后移 1 位锅锨,空出index處給新元素用
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 上一步已將index處的位置空出來,現(xiàn)在將新元素添加到index處
    elementData[index] = element;
    // 實際存儲的元素數(shù)加1
    size++;
}

2.1恋沃、刪除元素 public boolean remove(Object o)

public boolean remove(Object o) {
    // 兩種情況必搞,被刪除的元素是否為 null
    // for 循環(huán)遍歷、判斷囊咏、刪除(請注意:千萬不要使用 foreach 進(jìn)行刪除K≈蕖!C犯睢)
    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;
            }
    }
    // 如果刪除了霜第,返回為true,否則返回false户辞。
    return false;
}

/*
 * Private remove method that skips bounds checking and does not
 * return the value removed.
 * 跳過邊界檢查的私有移除方法泌类,并且不返回刪除的值
 */
private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 重點在這里,數(shù)據(jù)向前移動
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 清除 size-1 位置上的元素(設(shè)置空引用)底燎,讓GC去收集該元素刃榨,同時 size 減 1
    elementData[--size] = null; 
}

注意:

  1. 如果存在多個對象o,僅僅刪除距離 index = 0 處最近的一個元素;
  2. 元素被刪除后弹砚,該位置不會空出來,后面的元素會前移枢希。

所以 ArrayList 適合讀多刪少場景桌吃。

3、重新設(shè)置指定index位置的元素值 public E set(int index, E element)

public E set(int index, E element) {
    // 有index苞轿,必檢查
    rangeCheck(index);
    // 返回舊的(被重置的)元素
    E oldValue = elementData(index);
    // 直接把 index位置的元素覆蓋掉行了
    elementData[index] = element;
    return oldValue;
}

4茅诱、獲取指定index位置的元素 public E get(int index)

public E get(int index) {
   // 還是那句話,凡是遇到index索引呕屎,必檢查
    rangeCheck(index);
    // 直接返回index的元素让簿,數(shù)組查詢還不快嗎。
    return elementData(index);
}

E elementData(int index) {
    // 因為 ArrayList 本質(zhì)上是個數(shù)組秀睛,直接通過index獲取元素
    return (E) elementData[index];
}

5尔当、判斷元素是否存儲 public boolean contains(Object o) 判斷是否存在某個元素

public boolean contains(Object o) {
    // 簡單粗暴,直接遍歷查看index是否大于0
    return indexOf(o) >= 0;
}

// 查找元素是否存在蹂安,如果存在返回下標(biāo)椭迎,否則返回 -1
public int indexOf(Object o) {
    // 遍歷的時候分兩種情況,是否為 null
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            // 用 equals 判斷值是否相等
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

二田盈、ArrayList的親兄弟 Vector

Vector 的大部分方法是被 synchronized 修飾的畜号。
線程安全的ArrayList,幾乎所有的方法都加 synchronized 修飾允瞧;


三简软、Stack

Stack 繼承至 Vector,是一個后進(jìn)先出LIFO的隊列述暂。
它實現(xiàn)了一個標(biāo)準(zhǔn)的 后進(jìn)先出LIFO 的棧痹升。

原文持續(xù)更新鏈接: https://github.com/about-cloud/JavaCore

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市畦韭,隨后出現(xiàn)的幾起案子疼蛾,更是在濱河造成了極大的恐慌,老刑警劉巖艺配,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件察郁,死亡現(xiàn)場離奇詭異,居然都是意外死亡转唉,警方通過查閱死者的電腦和手機(jī)皮钠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赠法,“玉大人鳞芙,你說我怎么就攤上這事。” “怎么了原朝?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵驯嘱,是天一觀的道長。 經(jīng)常有香客問我喳坠,道長鞠评,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任壕鹉,我火速辦了婚禮剃幌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晾浴。我一直安慰自己负乡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布脊凰。 她就那樣靜靜地躺著抖棘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狸涌。 梳的紋絲不亂的頭發(fā)上切省,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機(jī)與錄音帕胆,去河邊找鬼朝捆。 笑死,一個胖子當(dāng)著我的面吹牛懒豹,可吹牛的內(nèi)容都是我干的芙盘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼脸秽,長吁一口氣:“原來是場噩夢啊……” “哼何陆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起豹储,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎淘这,沒想到半個月后剥扣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡铝穷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年钠怯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片曙聂。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡晦炊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情断国,我是刑警寧澤贤姆,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站稳衬,受9級特大地震影響霞捡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜薄疚,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一碧信、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧街夭,春花似錦砰碴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至檐什,卻和暖如春碴卧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乃正。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工住册, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓮具。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓荧飞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親名党。 傳聞我的和親對象是個殘疾皇子叹阔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

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