1. 程式人生 > >Collection原始碼之路(1)——ArrayList

Collection原始碼之路(1)——ArrayList

(以下原始碼建立在JDK 10版本基礎上)

ArrayList這個類用的實在是太頻繁了,除基本型別之外應該算是最常用了吧,但是一直用過卻一直不曾研究過裡面的原始碼,這是程式設計師的大忌,用什麼就要研究什麼,否則只是程式碼工匠談不上程式碼師。

在開始ArrayList學習之前,我一直有個疑問,就是陣列可以動態擴容嗎?我們知道java宣告陣列時,必須指明陣列的個數或者列舉出所有的元素(等於告訴編譯器自己長度為多少)

  String a[] = new String[]{"1","2","3"};
  String b[] = new String[10];
  String
c[] = new String[];//這一行編譯不過去

可是如果我們使用的時候發現數據太多了,陣列太小了,需要擴容怎麼辦呢?

沒辦法,新建一個數組,將舊的陣列資料拷貝到新數組裡面

 String a[] = new String[]{"1","2","3"};
 String b[] = new String[a.length+10];

例子比較簡單,這樣做尚可但是如果比較複雜的陣列怎麼處理長度呢?而且即使你處理完了發現數組又小了,該如何是好?繼續建立新的陣列嗎?這樣做太麻煩了!

建立一個可改變長度的陣列就好了——ArrayList

正因為有了這樣的需求,ArrayList橫空出世,ArrayList就是可改變長度的陣列

Resizable-array implementation of the {@code List} interface.

與此同時,其元素還包括null

Implements all optional list operations, and permits all elements, including {@code null}

Vector執行緒安全,ArrayList執行緒不安全

(This class is roughly equivalent to {@code Vector}, except that it is unsynchronized.)

其成員變數有下面幾個

  /**
     * Default initial capacity. 預設集合長度為10
     */
    private static final int DEFAULT_CAPACITY = 10;
   /**
     * Shared empty array instance used for empty instances.
     * 當你用new ArrayList(0)初始化的時候指定長度為0就會用到這個陣列
     */
    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.
     * 當你使用new ArrayList()生成集合的時候預設使用這個陣列,解釋具體看後面的程式碼
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
前面transient 表示是暫時的變數,序列化和反序列化都不會操作這個變數
這個變數什麼呢?就是在不斷變化的那個陣列,就好像我們使用p=head,在不破壞原有指標的情況下,構造了一個區域性變數來代替head指標,這個也是如此用於代替上面兩個陣列
transient Object[] elementData;
集合的長度
需要注意的是size是表示集合的長度,而後續原始碼中有elementData.length表示的是內部陣列的長度,這兩個不是一個概念!size是暴露給呼叫者的,elementData這個陣列是底層陣列,對於使用者是不可知的,他的長度不用和size相同
private int size;
集合最大長度,最大值-8,為啥-8我也不知道????
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

1 先看看我們常用的初始化操作 new ArrayList<>()

List<String> a=new ArrayList<>();
 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

也就是說如果我們使用無參構造方式建立的集合,使用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA這個陣列

2 再看看add()方法

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

如果發現數組已經滿了,程式會走進grow()方法,用於擴容這個陣列,操作成功之後集合長度+1

private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

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

最少也要在原來的陣列上增加1個,所以size+1,接下來我們看下Arrays.copyOf裡面的newCapacity方法,這個方法是獲取要擴容的長度,我們增加一個元素只擴容1的話豈不是每次擴容全部方法都要走一遍?不如多擴容點,這樣的話省的以後麻煩

 private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

int newCapacity = oldCapacity + (oldCapacity >> 1);
這句話的意思是新的陣列長度要是舊的1.5倍,也就是說擴容要擴充1.5倍,>>1表示除以2的意思
當集合為空的時候,newCapacity - minCapacity <= 0,又是DEFAULTCAPACITY_EMPTY_ELEMENTDATA陣列所以返回的擴容長度為DEFAULT_CAPACITY,所以預設集合長度為10

public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

new Object[newLength]是新建了一個數組

System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
這段程式碼很重要,陣列擴容就是用的這個方法

這句話的意思是:
將original陣列從0位置至original.length位置,複製到copy陣列0位置到Math.min位置。
為什麼做了一個取最小值操作呢?
因為有可能是陣列增加也有可能是陣列刪除,防止造成陣列下標越界。
經過了一系列操作,這個陣列變成了一個新陣列copy,陣列長度也變化了

3 再看看get()方法

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

Objects.checkIndex(index, size);這句話是檢驗是否下標越界,如果越界的話會丟擲異常
本身就是陣列,所以取相應索引的資料也很容易看懂

4 remove方法

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

這也很容易只是找到對應object的索引,具體刪除的工作交給了fastRemove方法
found:{}這個是語句塊命名的意思,現在基本上不這麼寫了,估計是作者寫1.2java的時候還是忘不掉c語言的goto語句就這麼寫了,就是返回個索引而已,很簡單

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

如果要刪除的元素在最後一個,也就是說(newSize = size - 1) == i的話,沒有必要運算元組,直接陣列的最後一個數據為空就好了,但是如果是前面的某個資料要被刪除呢?

System.arraycopy(es, i + 1, es, i, newSize - i);

又是這句話,意思是把i之後的元素複製到i個位置開始到newSize - i長度,這個有點繞,舉個例子

陣列     1 2 3 4
我想刪除第二個元素,下標是1
變成新陣列 1 3 4

把2刪除掉,34前移

5 addAll()方法

   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;
        if (numNew > (elementData = this.elementData).length - (s = size))
            elementData = grow(s + numNew);
        System.arraycopy(a, 0, elementData, s, numNew);
        size = s + numNew;
        return true;
    }

增加的操作之前已經說過啦,但是有一點需要說下,專案中是不允許addAll空的集合的,因為作者在第一句Object[] a = c.toArray();使用的時候就沒有做判空操作,所以我專案中就遇到過原始碼的空指標異常,把我嚇壞了~~~

6 clear方法

   public void clear() {
        modCount++;
        final Object[] es = elementData;
        for (int to = size, i = size = 0; i < to; i++)
            es[i] = null;
    }

陣列還在,只是裡面每個資料變成空了而已,但是size已經被賦值成了0,也就是說對於外部來說這個集合為空但是實際上裡面還是有陣列的。

好啦,以上就是ArrayList的大致原始碼總結,歡迎讀者朋友下方留言。

下一篇 Collection原始碼之路(2)——LinkedList