ArrayList源碼閱讀

對于ArrayList源碼,我是初次閱讀范舀,可能有很多地方理解不正確,如果有錯(cuò)的話還請大家多多指教了罪。

然后聲明一下的我的JDK版本:openjdk11锭环。(可以直接打開鏈接查看,鏈接的jdk11和我的版本稍有不同泊藕,但基本上一致)

首先說明一下ArrayList變量的含義

   /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 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

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;
  • DEFAULT_CAPACITY

對于這個(gè)值辅辩,需要說明一點(diǎn),當(dāng)你使用ArrayList<Integer> list = new ArrayList<>();定義一個(gè)list時(shí)娃圆,不是說你不給它賦值玫锋,它默認(rèn)的容量大小就是10了,具體信息會(huì)在add函數(shù)那里說明踊餐。

  • EMPTY_ELEMENTDATA

這個(gè)變量主要是當(dāng)elementData.length為0或者size為0時(shí)景醇,為elementData賦一個(gè)空數(shù)組用的。

  • DEFAULTCAPACITY_EMPTY_ELEMENTDATA

對于這個(gè)變量吝岭,看著好像和EMPTY_ELEMENTDATA沒什么區(qū)別三痰,但是他倆的作用是不一樣的吧寺,當(dāng)我們不指定initialCapacity時(shí),他會(huì)用DEFAULTCAPACITY_EMPTY_ELEMENTDATA為elementData進(jìn)行初始化散劫,之后會(huì)通過判斷elementData與該變量是不是相等來確定是不是第一次操作稚机。

  • elementData

這個(gè)變量應(yīng)該沒什么問題,就是用來存儲(chǔ)數(shù)據(jù)的

  • size

這個(gè)變量用于存儲(chǔ)當(dāng)前ArrayList有多少個(gè)元素(注:不是elementData數(shù)組的大谢癫)

  • modCount

這個(gè)變量是來自AbstractList赖条,我感覺很有必要說一下,它是用來記錄數(shù)組變化的次數(shù)(添加刪除等操作都算)

add函數(shù)

    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length) {
            elementData = this.grow();
        }

        elementData[s] = e;
        this.size = s + 1;
    }

grow()函數(shù)

    private Object[] grow(int minCapacity) {
        //使用Arrays.copyOf將原來的數(shù)據(jù)拷貝到新的數(shù)組中
        return this.elementData = Arrays.copyOf(this.elementData, this.newCapacity(minCapacity));
    }

從這可以看出ArrayList擴(kuò)容還是比較耗時(shí)的常熙,如果一開始能夠確認(rèn)數(shù)組的大小纬乍,那就為他賦一個(gè)初始容量,防止它多次擴(kuò)容而影響性能裸卫。

newCapacity()函數(shù)

    private int newCapacity(int minCapacity) {
        int oldCapacity = this.elementData.length;
        //新生成的容量大小是原來的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            //如果elementData是第一次擴(kuò)容仿贬,那就在minCapaticy和10中選一個(gè)最大值
            if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                return Math.max(10, minCapacity);
            } else if (minCapacity < 0) {
                throw new OutOfMemoryError();
            } else {
                return minCapacity;
            }
        } else {
            //hugeCapacity主要是防止數(shù)組大于2147483639
            return newCapacity - 2147483639 <= 0 ? newCapacity : hugeCapacity(minCapacity);
        }
    }

對于this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA這句我所是第一次擴(kuò)容,其實(shí)也不完全正確墓贿,這里必須是在new一個(gè)ArrayList時(shí)候沒有給它賦初始值茧泪。

上面三段代碼就一塊分析了,整個(gè)添加流程大概如下圖:


ArrayList.png

這幅圖可能并不是很準(zhǔn)確聋袋,有些細(xì)節(jié)我并沒有體現(xiàn)队伟,因?yàn)榕聢D越來越臃腫。

get()函數(shù)

    public E get(int index) {
        Objects.checkIndex(index, this.size);
        return this.elementData(index);
    }

這個(gè)函數(shù)很簡單幽勒,基本沒有要介紹的嗜侮,其中有個(gè)Objects.checkIndex(index, this.size)檢查下標(biāo)是否越界。

hashCode()函數(shù)

    public int hashCode() {
        int expectedModCount = this.modCount;
        int hash = this.hashCodeRange(0, this.size);
        //這里不是很確定代嗤,應(yīng)該是防止并發(fā)操作導(dǎo)致數(shù)組更改,導(dǎo)致求出的hash值與當(dāng)前值不一致(下面我也寫了一個(gè)示例)
        this.checkForComodification(expectedModCount);
        return hash;
    }

    int hashCodeRange(int from, int to) {
        Object[] es = this.elementData;
        if (to > es.length) {
            throw new ConcurrentModificationException();
        } else {
            int hashCode = 1;

            for(int i = from; i < to; ++i) {
                Object e = es[i];
                hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
            }

            return hashCode;
        }
    }

這里解釋一下hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode())為什么要乘31.

在Effective Java中作者是這樣解釋的:

乘法部分使得散列值依賴與域的順序棘钞,如果一個(gè)類包含多個(gè)相似的域,這樣的乘法運(yùn)算就會(huì)產(chǎn)生一個(gè)更好的散列函數(shù)干毅。例如宜猜,ArrayList中添加了1,2,3,4這四個(gè)元素,另一個(gè)ArrayList也添加了這四個(gè)元素硝逢,但是和第一個(gè)順序不一致姨拥,不用乘法的話,那這兩個(gè)ArrayList的hashCode就是一樣的(這里是我自己的理解渠鸽,下面我也給出了一個(gè)例子)叫乌。之所以選31是因?yàn)樗且粋€(gè)奇素?cái)?shù),如果乘數(shù)是偶數(shù)徽缚,并且乘法溢出的話憨奸,信息就會(huì)丟失,因?yàn)榕c2相乘等價(jià)與移位運(yùn)算凿试,使用素?cái)?shù)的好處并不明顯排宰,但習(xí)慣上都使用素?cái)?shù)來計(jì)算散列結(jié)果似芝。31有個(gè)很好特性,即用移位和減法來代替乘法(31 * i == (i << 5) - i)

  • checkForComodification的作用

示例1:


ArrayList3.jpg

測試結(jié)果:

ArrayList4.jpg

我一開始起了10個(gè)線程板甘,發(fā)現(xiàn)并沒有出現(xiàn)想要的異常党瓮,于是直接起1000個(gè)線程,不出意外盐类,出現(xiàn)了想要的結(jié)果寞奸。當(dāng)然我也不能100%確定就是這個(gè)作用,但是依照目前測試結(jié)果來看在跳,肯定是有這么個(gè)作用的枪萄。如果大家有別的見解歡迎指出。

  • 關(guān)于hashCode的測試

示例1:


ArrayList1.jpg

測試結(jié)果為true硬毕。

示例2:


ArrayList2.jpg

結(jié)果為false呻引。

可以知道循序不同,hashCode確實(shí)是不同的吐咳,這也就是乘31所起的作用。

remove函數(shù)

  • remove(int index)
    public E remove(int index) {
         //檢查下標(biāo)是否越界
        Objects.checkIndex(index, this.size);
        Object[] es = this.elementData;
        E oldValue = es[index];
        this.fastRemove(es, index);
        return oldValue;
    }

    private void fastRemove(Object[] es, int i) {
        ++this.modCount;
        int newSize;
        //如果不大于i元践,說明待刪除的是最后一個(gè)韭脊,直接將其置null
        if ((newSize = this.size - 1) > i) {
            //如果大于,調(diào)用本地方法進(jìn)行刪除
            System.arraycopy(es, i + 1, es, i, newSize - i);
        }

        es[this.size = newSize] = null;
    }
  • remove(Object o)
    public boolean remove(Object o) {
        Object[] es = this.elementData;
        int size = this.size;
        int i = 0;
        //如果待刪除的為null单旁,進(jìn)入if語句
        if (o == null) {
            while(true) {
                //找不到返回false
                if (i >= size) {
                    return false;
                }
                //找到跳出循環(huán)
                if (es[i] == null) {
                    break;
                }
                ++i;
            }
            //不為null沪羔,進(jìn)入else(下面同理)
        } else {
            while(true) {
                if (i >= size) {
                    return false;
                }

                if (o.equals(es[i])) {
                    break;
                }

                ++i;
            }
        }
        //找到下標(biāo)后就可以調(diào)用fastRemove了,和上面的remove流程就一樣了
        this.fastRemove(es, i);
        return true;
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末象浑,一起剝皮案震驚了整個(gè)濱河市蔫饰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌愉豺,老刑警劉巖篓吁,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚪拦,居然都是意外死亡杖剪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門驰贷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盛嘿,“玉大人,你說我怎么就攤上這事括袒〈握祝” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵锹锰,是天一觀的道長芥炭。 經(jīng)常有香客問我漓库,道長,這世上最難降的妖魔是什么蚤认? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任米苹,我火速辦了婚禮,結(jié)果婚禮上砰琢,老公的妹妹穿的比我還像新娘蘸嘶。我一直安慰自己,他們只是感情好陪汽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布训唱。 她就那樣靜靜地躺著,像睡著了一般挚冤。 火紅的嫁衣襯著肌膚如雪况增。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天训挡,我揣著相機(jī)與錄音澳骤,去河邊找鬼。 笑死澜薄,一個(gè)胖子當(dāng)著我的面吹牛为肮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肤京,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼颊艳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了忘分?” 一聲冷哼從身側(cè)響起棋枕,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎妒峦,沒想到半個(gè)月后重斑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舟山,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年绸狐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片累盗。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡寒矿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出若债,到底是詐尸還是另有隱情符相,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站啊终,受9級(jí)特大地震影響镜豹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蓝牲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一趟脂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧例衍,春花似錦昔期、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至梦抢,卻和暖如春般贼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奥吩。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工哼蛆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人霞赫。 一個(gè)月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓人芽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绩脆。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355