迎難而上ArrayList,源碼分析走一波

先看再點贊,給本身一點思考的時間,思考事後請堅決果斷微信搜索【沉默王二】,關注這個長髮飄飄卻靠才華苟且的程序員。
本文 GitHub github.com/itwanger 已收錄,裏面還有技術大佬整理的面試題,以及二哥的系列文章。java

關於 Java 基礎、Java 面向對象編程、Java 字符串、Java 數組等方面的知識點已經能夠告一段落了,小夥伴們能夠在「沉默王二」公衆號後臺回覆「小白」獲取第二版手冊。以爲不錯的話,請隨手轉發給身邊的小夥伴,贈人玫瑰,手有餘香哈。git

那麼接下來,我開始肝 Java 集合方面的文章了,小夥伴們請默默爲我鼓個掌,我能聽獲得,真的,別吝嗇你的掌聲,響起來。第一篇,必須得從 ArrayList 開始,畢竟 ArrayList 能夠稱得上是集合方面最經常使用的類了,估計沒有之一。程序員

ArrayList 實現了 List 接口,是基於數組實現的。小夥伴們都知道,數組的大小是固定的,建立的時候指定了大小,就不能再調整了,若是數組滿了,就不能再添加任何元素了。ArrayList 是數組很好的替代方案,它提供了比數組更豐富的預約義方法(增刪改查),而且大小是能夠根據元素的多少進行自動調整的,很是靈活。github

準備在 ArrayList 的第四個位置(下標爲 3)上添加一個元素 55。web

此時 ArrayList 中第五個位置之後的元素將會向後移動。面試

準備把 23 從 ArrayList 中移除。編程

此時下標爲 七、八、9 的元素往前挪。json

0一、如何建立一個 ArrayList

ArrayList<String> alist = new ArrayList<String>();

能夠經過上面的語句來建立一個字符串類型的 ArrayList(經過尖括號來限定 ArrayList 中元素的類型,若是嘗試添加其餘類型的元素,將會產生編譯錯誤),更簡化的寫法以下:數組

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

因爲 ArrayList 實現了 List 接口,因此 alist 變量的類型能夠是 List 類型;new 關鍵字聲明後的尖括號中能夠再也不指定元素的類型,由於編譯器能夠經過前面尖括號中的類型進行智能推斷。安全

若是很是肯定 ArrayList 中元素的個數,在建立的時候還能夠指定初始大小。

List<String> alist = new ArrayList<>(20);

這樣作的好處是,能夠有效地避免在添加新的元素時進行沒必要要的擴容。但一般狀況下,咱們很難肯定 ArrayList 中元素的個數,所以通常不指定初始大小。

0二、向 ArrayList 中添加一個元素

能夠經過 add() 方法向 ArrayList 中添加一個元素,若是不指定下標的話,就默認添加在末尾。

alist.add("沉默王二");

感興趣的小夥伴能夠研究一下 add() 方法的源碼,它在添加元素的時候會執行 grow() 方法進行擴容,這個是面試官特別喜歡考察的一個重點。

下面是 add(E e) 方法的源碼:

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

調用了私有的 add(E e, Object[] elementData, int s) 方法:

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

而後調用了很是關鍵的 grow(int minCapacity) 方法:

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

若是建立 ArrayList 的時候沒有指定初始大小,那麼 ArrayList 的初始大小就是 DEFAULT_CAPACITY:

private static final int DEFAULT_CAPACITY = 10;

能夠容納 10 個元素。

還能夠經過 add(int index, E element) 方法把元素添加到指定的位置:

alist.add(0"沉默王三");

add(int index, E element) 方法的源碼以下:

public void add(int index, E element) {
    rangeCheckForAdd(index);
    modCount++;
    final int s;
    Object[] elementData;
    if ((s = size) == (elementData = this.elementData).length)
        elementData = grow();
    System.arraycopy(elementData, index,
            elementData, index + 1,
            s - index);
    elementData[index] = element;
    size = s + 1;
}

該方法會調用到一個很是重要的本地方法 System.arraycopy(),它會對數組進行復制(要插入位置上的元素日後複製,參照文章一開頭提到的兩張圖片)。

0三、更新 ArrayList 中的元素

可使用 set() 方法來更改 ArrayList 中的元素,須要提供下標和新元素。

alist.set(0"沉默王四");

原來 0 位置上的元素爲「沉默王三」,如今將其更新爲「沉默王四」。

來看一下 set() 方法的源碼:

public E set(int index, E element) {
    Objects.checkIndex(index, size);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

該方法會先對指定的下標進行檢查,看是否越界,而後替換新值並返回舊值。

0四、刪除 ArrayList 中的元素

remove(int index) 方法用於刪除指定下標位置上的元素,remove(Object o) 方法用於刪除指定值的元素。

alist.remove(1);
alist.remove("沉默王四");

先來看 remove(int index) 方法的源碼:

public E remove(int index) {
    Objects.checkIndex(index, size);
    final Object[] es = elementData;

    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    fastRemove(es, index);

    return oldValue;
}

該方法返回要刪除的元素,真正的刪除操做在 fastRemove(es, index) 方法中。

再來看 remove(Object o) 方法的源碼:

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;
}

該方法經過 break label 的方式找到要刪除元素(null 的時候使用 == 操做符判斷,非 null 的時候使用 equals() 方法,意味着若是有相同元素時,刪除第一個)的下標,而後調用 fastRemove() 方法。

既然都調用了 fastRemove() 方法,那就繼續來跟蹤一下源碼:

private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}

當刪除的是末尾的元素時,不須要複製數組,直接把末尾的元素賦值爲 null 便可;不然的話,就須要調用 System.arraycopy() 對數組進行復制。參照文章一開頭提到的第三張、第四張圖片。

0五、查找 ArrayList 中的元素

若是要正序查找一個元素,可使用 indexOf() 方法;若是要倒序查找一個元素,可使用 lastIndexOf() 方法。

alist.indexOf("沉默王二");
alist.lastIndexOf("沉默王二");

來看一下 indexOf() 方法的源碼:

public int indexOf(Object o) {
    return indexOfRange(o, 0, size);
}

int indexOfRange(Object o, int start, int end) {
    Object[] es = elementData;
    if (o == null) {
        for (int i = start; i < end; i++) {
            if (es[i] == null) {
                return i;
            }
        }
    } else {
        for (int i = start; i < end; i++) {
            if (o.equals(es[i])) {
                return i;
            }
        }
    }
    return -1;
}

若是元素爲 null 的時候使用「==」操做符,不然使用 equals() 方法——該方法不是 null 安全的。

lastIndexOf() 方法和 indexOf() 方法相似,不過遍歷的時候從最後開始。

contains() 方法能夠判斷 ArrayList 中是否包含某個元素,其內部調用了 indexOf() 方法:

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

若是 ArrayList 中的元素是通過排序的,就可使用二分查找法,效率更快。

Collections 類的 sort() 方法能夠對 ArrayList 進行排序,該方法會按照字母順序對 String 類型的列表進行排序。若是是自定義類型的列表,還能夠指定 Comparator 進行排序。

List<String> copy = new ArrayList<>(alist);
copy.add("a");
copy.add("c");
copy.add("b");
copy.add("d");

Collections.sort(copy);
System.out.println(copy);

輸出結果以下所示:

[a, b, c, d]

排序後就可使用二分查找法了:

int index = Collections.binarySearch(copy, "b");

0六、最後

關於 ArrayList,就先介紹這麼多吧,經過源碼的角度,我想小夥伴們必定對 ArrayList 有了更深入的印象。

簡單總結一下 ArrayList 的時間複雜度,方便後面學習 LinkedList 時做爲一個對比。

1)經過下標(也就是 get(int index))訪問一個元素的時間複雜度爲 O(1),由於是直達的,不管數據增大多少倍,耗時都不變。

2)添加一個元素(也就是 add())的時間複雜度爲 O(1),由於直接添加到末尾。

3)刪除一個元素的時間複雜度爲 O(n),由於要遍歷列表,數據量增大幾倍,耗時也增大幾倍。

4)查找一個未排序的列表時間複雜度爲 O(n),由於要遍歷列表;查找排序過的列表時間複雜度爲 O(log n),由於可使用二分查找法,當數據增大 n 倍時,耗時增大 logn 倍(這裏的 log 是以 2 爲底的,每找一次排除一半的可能)。


我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注便可提高學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,奧利給

注:若是文章有任何問題,歡迎絕不留情地指正。

不少讀者都同情我說,「二哥,你像母豬似的日更原創累不累?」我只能說寫做不易,且行且珍惜啊,關鍵是我真的喜歡寫做。最後,歡迎微信搜索「沉默王二」第一時間閱讀,回覆「簡歷」更有阿里大佬的簡歷模板,本文 GitHub github.com/itwanger 已收錄,歡迎 star。

相關文章
相關標籤/搜索