Java集合(二) —— ArrayList源碼分析

Java集合(一) —— Collection源碼分析
Java集合(二) —— ArrayList源碼分析
Java集合(三) —— LinkedList源碼分析
Java集合(四) —— PriorityQueue源碼分析
Java集合(五) —— HashSet源碼分析
Java集合(六) —— LinkedHashSet源碼分析
Java集合(七) —— TreeSet源碼分析
Java集合(八) —— HashMap源碼分析
Java集合(九) —— LinkedHashMap源碼分析
Java集合(十) —— TreeMap源碼分析

1.ArrayList繼承圖

ArrayList.png

2.ArrayList的數(shù)據(jù)結(jié)構(gòu)

// 源碼
/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData; // non-private to simplify nested class access

可以看到ArrayList的底層是通過數(shù)組實(shí)現(xiàn)的旬渠。
數(shù)組(Array)是一種線性表數(shù)據(jù)結(jié)構(gòu)馏颂,用一組連續(xù)的內(nèi)存空間,來存儲(chǔ)一組具有相同類型的數(shù)據(jù)猛拴。

3.ArrayList性能分析

1.因?yàn)锳rrayList是基于數(shù)組實(shí)現(xiàn)的遇绞,所以可以通過下標(biāo)快速訪問元素(包括get&set),其時(shí)間復(fù)雜度為O(1)。
2.增加/刪除元素:最理想的狀況是從數(shù)組末尾增加或刪除元素巧号,不需要移動(dòng)數(shù)據(jù),時(shí)間復(fù)雜度為O(1);最糟糕的狀況是從頭部新增或刪除一個(gè)元素姥闭,導(dǎo)致所有的元素都要移動(dòng)一位丹鸿,時(shí)間復(fù)雜度為O(n);
3.因?yàn)槊總€(gè)位置插入/刪除元素的概率是一樣的棚品,所以平均時(shí)間復(fù)雜度為(1+2+…n)/n=O(n)
總結(jié)(對比LinkedList):

  • ArrayList具有較好的查詢性能靠欢,而新增/刪除都可能引起大量元素的移動(dòng),性能較差铜跑,所以ArrayList適用于查詢較多门怪,新增/刪除較少的場景。
  • LinkedList(下一章會(huì)介紹)是基于鏈表的锅纺,在查詢(隨機(jī)訪問)時(shí)性能較差掷空,因?yàn)樾枰陔p向鏈表中尋找index的位置再返回,所以LinkedList適用于查詢較少,新增/刪除較多的場景坦弟。
  • LinkedList需要額外的內(nèi)存空間保存索引信息护锤。

4.源碼分析

  1. 成員變量分析
    /**
     * 默認(rèn)初始容量大小為 10
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 指定該ArrayList容量為0時(shí),返回該空數(shù)組酿傍。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 當(dāng)調(diào)用無參構(gòu)造方法烙懦,返回的是該數(shù)組。剛創(chuàng)建一個(gè)ArrayList 時(shí)拧粪,其內(nèi)數(shù)據(jù)量為0修陡。
     * 它與EMPTY_ELEMENTDATA的區(qū)別就是:該數(shù)組是默認(rèn)返回的,而后者是在用戶指定容量為0時(shí)返回可霎。
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 存儲(chǔ)集合元素的底層實(shí)現(xiàn):真正存放元素的數(shù)組魄鸦。
     * 被標(biāo)記為transient,在對象被序列化的時(shí)候不會(huì)被序列化癣朗。
     */
    transient Object[] elementData; 

    /**
     * 實(shí)際元素?cái)?shù)量
     */
    private int size;
  1. 構(gòu)造函數(shù)分析
/**
 * 默認(rèn)將elementData 指向一個(gè)空數(shù)組拾因,即其大小為0,要在第一次添加元素時(shí)容量才擴(kuò)大至10旷余。(可以認(rèn)為是一種懶加載機(jī)制绢记,避免浪費(fèi)內(nèi)存空間)
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * 指定容量的構(gòu)造函數(shù)
 */
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);
    }
}

/**
 * 使用指定集合構(gòu)造ArrayList
 */
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;
    }
}

3.核心方法分析

  • add(E e)
public boolean add(E e) {
    // 檢查是否需要擴(kuò)容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 新增元素
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 第一次添加元素時(shí),minCapacity = 1正卧,所以返回默認(rèn)容量DEFAULT_CAPACITY = 10
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
    // list被修改次數(shù)
    modCount++;

    // overflow-conscious code
    // minCapacity  = 10
    if (minCapacity - elementData.length > 0)
        // 進(jìn)行擴(kuò)容
        grow(minCapacity);
}

/**
 * 擴(kuò)容
 */
private void grow(int minCapacity) {
    // 當(dāng)前數(shù)組容量
    int oldCapacity = elementData.length;
    // 擴(kuò)容新容量
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果擴(kuò)容后容量仍小于期望的最小容量
    if (newCapacity - minCapacity < 0)
        // 將期望的最小容量賦值給新容量
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        // 如果擴(kuò)容后的容量大于臨界值蠢熄,則進(jìn)行大容量分配
        newCapacity = hugeCapacity(minCapacity);
    // 進(jìn)行數(shù)組復(fù)制
    elementData = Arrays.copyOf(elementData, newCapacity);
}

關(guān)于擴(kuò)容
int newCapacity = oldCapacity + (oldCapacity >> 1);
首先說明,擴(kuò)容并非網(wǎng)上所說在原來基礎(chǔ)上增加一半炉旷。oldCapacity >> 1是位運(yùn)算签孔,假設(shè)oldCapacity = 10,則newCapacity = 15窘行;下一次擴(kuò)容時(shí)饥追,oldCapacity = 15,newCapacity = 22罐盔。(不懂位運(yùn)算的請自行百度)

  • add(int index, E element)
public void add(int index, E element) {
    // 數(shù)組越界檢查
    rangeCheckForAdd(index);
    // 檢查是否需要擴(kuò)容
    ensureCapacityInternal(size + 1);
    // 拷貝數(shù)組 
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 指定位置新增元素
    elementData[index] = element;
    // 數(shù)組大小+1
    size++;
}
  • remove(int index)
public E remove(int index) {
    // 下標(biāo)越界檢查
    rangeCheck(index);
 
    // 修改次數(shù)+1
    modCount++;
    E oldValue = elementData(index);

    // 需要移動(dòng)的元素?cái)?shù)量
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 移動(dòng)元素操作
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}
  • get(int index)
public E get(int index) {
    // 數(shù)組越界檢查
    rangeCheck(index);
    // fail-fast:快速失敗機(jī)制
    checkForComodification();
    return ArrayList.this.elementData(offset + index);
}

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

關(guān)于快速失敗機(jī)制
fail-fast 機(jī)制但绕,即快速失敗機(jī)制,是java集合(Collection)中的一種錯(cuò)誤檢測機(jī)制惶看。當(dāng)在迭代集合的過程中該集合在結(jié)構(gòu)上發(fā)生改變的時(shí)候捏顺,就有可能會(huì)發(fā)生fail-fast,即拋出 ConcurrentModificationException異常纬黎。fail-fast機(jī)制并不保證在不同步的修改下一定會(huì)拋出異常草丧,它只是盡最大努力去拋出,所以這種機(jī)制一般僅用于檢測bug莹桅。

  • clear()
public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}
  • set(int index, E element)
/**
 * 替換指定索引元素
 */
public E set(int index, E element) {
    // 數(shù)組越界檢查
    rangeCheck(index);

    // 記錄舊的元素
    E oldValue = elementData(index);
    // 替換元素
    elementData[index] = element;
    return oldValue;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子诈泼,更是在濱河造成了極大的恐慌懂拾,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铐达,死亡現(xiàn)場離奇詭異岖赋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瓮孙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門唐断,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人杭抠,你說我怎么就攤上這事脸甘。” “怎么了偏灿?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵丹诀,是天一觀的道長。 經(jīng)常有香客問我翁垂,道長铆遭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任沿猜,我火速辦了婚禮枚荣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘啼肩。我一直安慰自己橄妆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布疟游。 她就那樣靜靜地躺著呼畸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颁虐。 梳的紋絲不亂的頭發(fā)上蛮原,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音另绩,去河邊找鬼儒陨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛笋籽,可吹牛的內(nèi)容都是我干的蹦漠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼车海,長吁一口氣:“原來是場噩夢啊……” “哼笛园!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤研铆,失蹤者是張志新(化名)和其女友劉穎埋同,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棵红,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凶赁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逆甜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虱肄。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖交煞,靈堂內(nèi)的尸體忽然破棺而出咏窿,到底是詐尸還是另有隱情,我是刑警寧澤错敢,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布翰灾,位于F島的核電站,受9級(jí)特大地震影響稚茅,放射性物質(zhì)發(fā)生泄漏纸淮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一亚享、第九天 我趴在偏房一處隱蔽的房頂上張望咽块。 院中可真熱鬧,春花似錦欺税、人聲如沸侈沪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽亭罪。三九已至,卻和暖如春歼秽,著一層夾襖步出監(jiān)牢的瞬間应役,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工燥筷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留箩祥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓肆氓,卻偏偏與公主長得像袍祖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子谢揪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354