arraylist源碼分析

1、前言
說到ArrayList,就不得不說Array。光看名字,還以為這2個是同一個東西。其實不然。
Array:指容量為固定的數組,常見的初始化方法如下:

String[] names = {"david","tom","kate"};

在聲明的時候直接聲明了內部元素,這樣jvm就可以快速的分配給指定大小的空間。同時,看Array源碼可知,Array的方法基本上都是native方法,其底層實現均為c/c++實現。

ArrayList:動態數組,允許新增、刪除等操作,常見的初始化方法:

List<String> nameList = new ArrayList<>();

看完概念,我們開始正題,開始源碼的解讀。

2、與其他容器對比

與hashmap不同,ArrayList為集合的一種,來源自數據結構中的數組概念,與hashmap源自數據結構中的圖概念不同。

我們先看ArrayList的uml圖。

image

可以看出,ArrayList不僅實現了Cloneable、Serializable接口,還 實現了RandomAccess接口、List接口。

3、簡述RandomAccess接口
在后續代碼中,還可以看到RandomAccess這個接口。若實現了該接口,則表明該類可以進行下標式訪問,類似于這樣:

List<String> nameList = new ArrayList<>();
String david  = names[0];

(這個會提示越界了)

4、成員變量分析
ArrayList的成員變量分析如下:

//默認容量
private static final int DEFAULT_CAPACITY = 10;
//空數組
private static final Object[] EMPTY_ELEMENTDATA = {};
//也是空數組,作用是在新增元素的時候用
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//數組
transient Object[] elementData;
//數組大小
private int size;
//數組最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//繼承自AbstractList,用于fail-fast機制
protected transient int modCount = 0;

從成員變量來看,數組用transient修飾符修飾,作用應該是類似于hashmap的Node[]。

5、核心方法分析

5.1 容量擴增

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //新空間分配直接擴大50%
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //得出較大的值
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //元素復制
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

private static int hugeCapacity(int minCapacity) {
        //這一點比較特殊,見下面分析
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

關于hugeCapacity的判斷小于0則為溢出,由于在jvm內部是以反碼存儲的數據,首位為符號位,當容量擴增后,若溢出,首位則變為1,此時變為負數,則可以快速判斷出是否溢出。

5.2 trimToSize 壓縮空間

去掉多余的空對象,精簡存儲空間

public void trimToSize() {
        modCount++;
        //代碼也寫的很簡潔,經常使用三元表達式
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

由上面的擴增容量可知,如果原始容量是100,在擴增容量后,那么分配的容量為150.但是實際上可能只存110個對象實例,那么此時調用這個方法,就可以節約一定的存儲空間。不過若數組較大,那么操作可能會耗費一點時間。

5.3 fail-fast機制
這個其實在hashmap中也采取了類似機制,就是額外有一個成員變量,用于快速判斷該實例是否有變化,若在進行迭代的時候有變更,那么就拋出一個并發修改異常(ConcurrentModificationException)。

5.4 indexOf 求 下標

public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
}

這段代碼略過,用的比較多,沒什么亮點。

5.5 新增一個元素

既然是數組,那么就有兩種 新增的方式:指定特定的位置、向后插入。

向指定位置寫入元素:

public void add(int index, E element) {
        //下標檢查,是否越界了
        rangeCheckForAdd(index);
        //擴增容量,同時改變modcount
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //index后面的元素后移
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        //指定位置放置元素
        elementData[index] = element;
        //元素數量大小自增
        size++;
    }

向后插入一個元素:

public boolean add(E e) {
        //擴大容量,修改modcount
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //注意
        //數組是從0開始的存元素的,而數組個數是從1開始計數的
        //這個地方是往第size個位置上存元素
        //再將元素個數加1
        elementData[size++] = e;
        return true;
    }

從以上源碼可以看出,向后插入一個元素不用進行元素的復制,自然效率要大于指定位置插入一個元素。

5.6 移除一個元素
同新增,也是分2種。
移除指定位置的元素:

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);
        //這個與新增類似,但是是左自減運算,自己體會吧
        //特地表明將該位置的置空,讓gc回收空間
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

刪除指定元素:

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
}

看到這個指定刪除某個元素的代碼,那么有人就會有疑問了,如果我里面有多個一樣的元素,那這不是刪不完嗎?這個時候你得使用removeAll:

public boolean removeAll(Collection<?> c) {
    //參數校驗,僅判斷c != null,感覺不太嚴謹
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}

private boolean batchRemove(Collection<?> c, boolean complement) {
    //常量數組,不允許再賦值,但是數組內部的元素允許自由移動、被重新賦值
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)
            //不包含該元素,則存到新數組,其實是位置前移
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        //上面if內類型不匹配拋異常時,r與size不等
        if (r != size) {
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {
            // clear to let GC do its work
            //由于將已包含的元素前移了,那么不包含的元素都在后面,直接將后面的元素刪掉
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}

5.7 序列化
這個源碼類似于hashmap,就不重復分析了。

5.8 其他
set、get方法比較簡單, 就不分析了。
需要注意的是,arraylist繼承的AbstractList覆寫了hashcode和equals方法。

6 小結
總的來說,arraylist的源碼比較簡單,可供分析的內容不多。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容

  • ArrayList是在Java中最常用的集合之一,其本質上可以當做是一個可擴容的數組,可以添加重復的數據,也支持隨...
    ShawnIsACoder閱讀 576評論 4 7
  • 前言 跟著我往下看(這里只講順序存儲方式的線性表): 不明白?繼續往下看 下面開始進入正題:ArrayList就是...
    小朱v閱讀 454評論 0 1
  • 以下源碼基于 Android SDK 23, 與JDK中略有差別,但基本相同;整體源碼由 構造、添加(add)、設...
    Jacky_Y閱讀 450評論 0 3
  • ArrayList 原文見:Java 容器源碼分析之 ArrayList 概述 ArrayList是使用頻率最高的...
    Leocat閱讀 237評論 0 0
  • ArrayList概述 arrayList的類圖如下: 類結構說明: 實現Iterable接口,說明該目標對象允許...
    blueSkyBird閱讀 361評論 0 1