Java之ArrayList實(shí)現(xiàn)原理(jdk11)

一. 構(gòu)造函數(shù)
二. 添加元素
2.1 添加一個元素到末尾
2.2 將指定的元素插入此列表中的指定位置
2.3 將一個指定collection中的所有元素添加到此列表的尾部
2.4 從指定的位置開始显蝌,將指定collection中的所有元素插入到此列表中
三. 用指定的元素替代此列表中指定位置上的元素
四. 刪除元素
4.1 移除此列表中指定位置上的元素
4.2 移除此列表中首次出現(xiàn)的指定元素 (如果存在)
4.3 從指定collection1中移除與指定collection2中相同的所有元素;
五. 調(diào)整數(shù)組容量
六. 將底層數(shù)組的容量調(diào)整為當(dāng)前列表保存的實(shí)際元素的大小
七. 總結(jié)

一. 構(gòu)造函數(shù)

ArrayList是List接口的可變數(shù)組的實(shí)現(xiàn)曼尊,實(shí)現(xiàn)了所有可選列表操作骆撇,并允許包括 null 在內(nèi)的所有元素父叙;底層使用數(shù)組保存所有元素,其操作基本上是對數(shù)組的操作,無序不唯一鲸匿;

transient Object[] elementData;
// ArrayList的大凶杓纭(所包含元素的個數(shù))
private int size;
// 用于空實(shí)例的共享空數(shù)組實(shí)例
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默認(rèn)的空數(shù)組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_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(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        if (elementData.getClass() != Object[].class)
            // 給elementData賦值,ArrayList實(shí)際存放元素的數(shù)組
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

從 ArrayList 的構(gòu)造函數(shù)中可以看到當(dāng)沒有給初始大小時, 默認(rèn)給初始數(shù)組賦值的是一個空數(shù)組;

二. 添加元素

2.1 添加一個元素到末尾
private static final int DEFAULT_CAPACITY = 10;

public boolean add(E e) {
    // 父類中的變量 用于檢測判斷 ConcurrentModificationException
    modCount++;
    add(e, elementData, size);
    return true;
}

private void add(E e, Object[] elementData, int s) {
    // 判斷是否應(yīng)該進(jìn)行擴(kuò)容
    if (s == elementData.length)
        // 對數(shù)組進(jìn)行擴(kuò)容的函數(shù)
        // 數(shù)組里面沒有元素時,第一次調(diào)grow()方法進(jìn)行擴(kuò)容,默認(rèn)大小為10;
        // 后面數(shù)組滿每次進(jìn)行擴(kuò)容時,每次擴(kuò)容原數(shù)組大小的1/2;
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

private Object[] grow() {
    return grow(size + 1);
}

private Object[] grow(int minCapacity) {
    // 將舊的數(shù)組元素復(fù)制到新的數(shù)組中,新的數(shù)組長度是10或者是擴(kuò)容后的數(shù)組長度;
    return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity));
}

private int newCapacity(int minCapacity) {
    int oldCapacity = elementData.length;
    // 每次擴(kuò)容增加百分之五十的容量
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity <= 0) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // 只有第一次添加元素擴(kuò)容時這里才會走進(jìn)來
            // 取數(shù)組容量和默認(rèn)容量10之間的最大值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        if (minCapacity < 0)
            throw new OutOfMemoryError();
        return minCapacity;
    }
    // MAX_ARRAY_SIZE為Integer的最大取值
    // 將擴(kuò)容后的數(shù)組大小返回(非實(shí)際所存元素的個數(shù))
    return (newCapacity - MAX_ARRAY_SIZE <= 0)
        ? newCapacity : hugeCapacity(minCapacity);
}

在 ArrayList 添加第一個元素時, 會調(diào) grow() 函數(shù)對初始數(shù)組進(jìn)行第一次擴(kuò)容并且大小為10, 也就是說ArrayList 此時的容量大小是10 但是它當(dāng)前所存儲的元素個數(shù)不一定是10;

后面數(shù)組滿, 每次進(jìn)行擴(kuò)容時, 每次會擴(kuò)大原容量大小的1/2;

2.2 將指定的元素插入此列表中的指定位置
public void add(int index, E element) {
    // 對 index 索引進(jìn)行越界檢測
    rangeCheckForAdd(index);
    modCount++;
    final int s;
    Object[] elementData;
    if ((s = size) == (elementData = this.elementData).length)
        elementData = grow();
    // 將舊數(shù)組復(fù)制到一個加大的新數(shù)組 并且從指定位置開始元素發(fā)生移位
    System.arraycopy(elementData, index,elementData, index + 1,s - index);
    elementData[index] = element;
    size = s + 1;
}

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

2.3 將一個指定collection中的所有元素添加到此列表的尾部
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    modCount++;
    int numNew = a.length;
    if (numNew == 0)
        return false;
    Object[] elementData;
    final int s;
    // 判斷是否需要擴(kuò)容
    if (numNew > (elementData = this.elementData).length - (s = size))
        elementData = grow(s + numNew);
    // 對數(shù)組 elementData 進(jìn)行復(fù)制元素進(jìn)去的操作
    System.arraycopy(a, 0, elementData, s, numNew);
    size = s + numNew;
    return true;
}
2.4 從指定的位置開始柒室,將指定collection中的所有元素插入到此列表中
public boolean addAll(int index, Collection<? extends E> c) {
    // 檢測索引是否越界
    rangeCheckForAdd(index);
    Object[] a = c.toArray();
    modCount++;
    int numNew = a.length;
    if (numNew == 0)
        return false;
    Object[] elementData;
    final int s;
    // 判斷是否需要擴(kuò)容
    if (numNew > (elementData = this.elementData).length - (s = size))
        elementData = grow(s + numNew);
    
    int numMoved = s - index;
    // 對數(shù)組 elementData 進(jìn)行復(fù)制元素進(jìn)去的操作
    if (numMoved > 0)
        System.arraycopy(elementData, index,elementData, index + numNew,numMoved);
    System.arraycopy(a, 0, elementData, index, numNew);
    size = s + numNew;
    return true;
}

三. 用指定的元素替代此列表中指定位置上的元素

public E set(int index, E element) {
    Objects.checkIndex(index, size);
    E oldValue = elementData(index);
    // 重新賦值
    elementData[index] = element;
    // 返回舊元素
    return oldValue;
}

四. 刪除元素

4.1 移除此列表中指定位置上的元素
public E remove(int index) {
    // 對索引進(jìn)行檢測
    Objects.checkIndex(index, size);
    final Object[] es = elementData;
    // 用變量保存index位置的元素 用于刪除成功后返回
    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    fastRemove(es, index);
    return oldValue;
}

private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        // 刪除一個元素后的數(shù)組需要進(jìn)行移位操作, ArrayList里都是復(fù)制到一個新的數(shù)組中;
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}
4.2 移除此列表中首次出現(xiàn)的指定元素 (如果存在)
public boolean remove(Object o) {
    final Object[] es = elementData;
    final int size = this.size;
    int i = 0;
    found: {
        if (o == null) {
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else {
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    fastRemove(es, i);
    return true;
}
4.3 從指定collection1中移除與指定collection2中相同的所有元素;
public boolean removeAll(Collection<?> c) {
    return batchRemove(c, false, 0, size);
}

boolean batchRemove(Collection<?> c, boolean complement,
                        final int from, final int end) {
    Objects.requireNonNull(c);
    final Object[] es = elementData;
    int r;
    // 第一個循環(huán)檢測兩個集合中是否有相同的元素
    for (r = from;; r++) {
        if (r == end)
            return false;// 兩個集合是沒有相同的元素
        if (c.contains(es[r]) != complement)
            break;
    }
    // 有相同的元素會繼續(xù)執(zhí)行
    int w = r++;
    try {
        for (Object e; r < end; r++)
            if (c.contains(e = es[r]) == complement)
                es[w++] = e;
    } catch (Throwable ex) { 
        System.arraycopy(es, r, es, w, end - r);
        w += end - r;
        throw ex;
    } finally {
        modCount += end - w;
        shiftTailOverGap(es, w, end);
    }
    return true;
}

示例測試

ArrayList<String> list = new ArrayList<>();
list.add("rzc");
list.add("rzc01");
list.add("rzc02");
list.add("rzc03");

ArrayList<String> list2 = new ArrayList<>();
list2.add("rzc");
list2.add("rzc01");
list2.add("rzc04");
list2.removeAll(list);
list.addAll(list2);
System.out.println(list.toString());
System.out.println(list2.toString());

打印結(jié)果

[rzc, rzc01, rzc02, rzc03, rzc04]
[rzc04]

五. 調(diào)整數(shù)組容量

public void ensureCapacity(int minCapacity) {
    if (minCapacity > elementData.length
        && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
             && minCapacity <= DEFAULT_CAPACITY)) {
        modCount++;
        grow(minCapacity);
    }
}

所需最小容量與數(shù)組大小作比較,大于需要擴(kuò)容囤屹,從前面的代碼中可以看出擴(kuò)容實(shí)際上是new了一個新的數(shù)組逢渔,然后將舊的數(shù)組中的元素復(fù)制到新的數(shù)組中;

六. 將底層數(shù)組的容量調(diào)整為當(dāng)前列表保存的實(shí)際元素的大小

public void trimToSize() {
    // 父類中的變量 用于檢測判斷 ConcurrentModificationException
    modCount++;
    // 將實(shí)際存儲的元素個數(shù)與真實(shí)容量做比較
    if (size < elementData.length) {
        elementData = (size == 0)
            ? EMPTY_ELEMENTDATA
            // 復(fù)制到一個容量更小的數(shù)組中
            : Arrays.copyOf(elementData, size);
    }
}

七. 總結(jié)

  • 每個 ArrayList 實(shí)例都有一個容量智厌,該容量是指用來存儲列表元素的數(shù)組的大小峦剔,它總是至少等于列表的大小并且最始默認(rèn)值為10;
  • 隨著向 ArrayList 中不斷添加元素吝沫,其容量也自動增長惨险;自動增長會帶來數(shù)據(jù)向新數(shù)組的重新拷貝,因此栅受,如果可預(yù)知數(shù)據(jù)量的多少恭朗,可在構(gòu)造 ArrayList 時指定其容量;
  • 在添加大量元素前而芥,應(yīng)用程序也可以使用ensureCapacity() 操作來增加 ArrayList 實(shí)例的容量膀值,這可以減少遞增式再分配的數(shù)量(就是先將其一次性擴(kuò)容,避免重復(fù)性擴(kuò)容操作)歌逢;
  • 可以通過trimToSize()函數(shù)將數(shù)組大小調(diào)整到所存儲實(shí)際元素的個數(shù)大小翘狱,以減少對內(nèi)存的消耗盒蟆;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末历等,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子寒屯,更是在濱河造成了極大的恐慌,老刑警劉巖处面,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件魂角,死亡現(xiàn)場離奇詭異,居然都是意外死亡访忿,警方通過查閱死者的電腦和手機(jī)斯稳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門挣惰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人珍语,你說我怎么就攤上這事竖幔。” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵饿幅,是天一觀的道長栗恩。 經(jīng)常有香客問我洪燥,道長,這世上最難降的妖魔是什么市咆? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任蒙兰,我火速辦了婚禮,結(jié)果婚禮上搜变,老公的妹妹穿的比我還像新娘挠他。我一直安慰自己,他們只是感情好殖侵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布愉耙。 她就那樣靜靜地躺著朴沿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪魏铅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天坚芜,我揣著相機(jī)與錄音览芳,去河邊找鬼。 笑死鸿竖,一個胖子當(dāng)著我的面吹牛沧竟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缚忧,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼悟泵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了闪水?” 一聲冷哼從身側(cè)響起糕非,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤球榆,失蹤者是張志新(化名)和其女友劉穎朽肥,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體持钉,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡衡招,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了每强。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚁吝。...
    茶點(diǎn)故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡旱爆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窘茁,到底是詐尸還是另有隱情怀伦,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布山林,位于F島的核電站房待,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏驼抹。R本人自食惡果不足惜桑孩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望框冀。 院中可真熱鬧流椒,春花似錦、人聲如沸明也。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽温数。三九已至绣硝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間撑刺,已是汗流浹背鹉胖。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留够傍,地道東北人甫菠。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像冕屯,于是被迫代替她去往敵國和親淑蔚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評論 2 355

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

  • (一)Java部分 1愕撰、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨(dú)云閱讀 7,105評論 0 62
  • categories: Interviewdescription: 本文收集了一些經(jīng)典的Java面試題 1、面向?qū)?..
    我是阿喵醬閱讀 87,848評論 0 86
  • 本系列出于AWeiLoveAndroid的分享醋寝,在此感謝搞挣,再結(jié)合自身經(jīng)驗查漏補(bǔ)缺,完善答案音羞。以成系統(tǒng)囱桨。 Java基...
    濟(jì)公大將閱讀 1,528評論 1 6
  • 引言 今年夏天,Keep首頁改版嗅绰,把強(qiáng)調(diào)用戶運(yùn)動時間積累的優(yōu)先級提到了最高舍肠〔蠹蹋“行走”功能群就是這個背景的產(chǎn)物,用戶...
    偽詩閱讀 14,787評論 0 3
  • 今天的情緒有波動翠语,1年半了叽躯,重新回歸職場,選擇了新的行業(yè)肌括,一切從零開始点骑。其實(shí)我很簡單,就想在新的行業(yè)好好學(xué)習(xí)谍夭,把每...
    菱520閱讀 142評論 0 0