天天看點

Java 容器源碼分析之 ArrayList

概覽

ArrayList是最常使用的集合類之一了。在JDK文檔中對

ArrayList

的描述是:

ArrayList

是對

list

接口的一種基于可變數組的實作。ArrayList類的聲明如下:

1
2
      
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
      

ArrayList繼承了AbstractList抽象類,并實作了List,RandomAccess,Cloneable以及Serializable接口。對 RandomAccess 接口的實作表明支援随機通路(因為基于數組嘛~),同Cloneable接口和Serializable接口一樣,該接口隻是一個标記,不需要實作任何方法。ArrayList 可以支援值為 null 的元素。

本文中的分析都是針對JDK8中的源碼進行的。

底層結構

從文檔中的說明可以知道,ArrayList的底層是基于數組來實作的。那我們就先來看一下

ArrayList

的成員變量:

1
2
3
4
5
6
7
8
9
10
11
      
private static final long serialVersionUID = 8683452581122892189L;

private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//The maximum size of array to allocate.
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

transient Object[] elementData;
private int size;
      

使用了一個 Object 數組來存放資料,并維護一個計對數器來記錄目前容器中元素的數量。注意到數組 elementData 是使用 transient 來修飾的,在後面會此進行進行解釋。

除此以外,在 ArrayList 還有一個繼承自父類 

AbstractList

 的成員變量 modCount 需要關注。使用 modCount 記錄清單發生結構化修改的次數,進而提供 fail-fast 的疊代器。因為 ArrayList 的實作是非同步的,如果在疊代過程中另一個線程向同一個容器中添加元素或移除元素,就會導緻

ConcurrentModificationExceptions

1
2
      
//The number of times this list has been structurally modified.
protected transient int modCount = 0;
      

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
      
/**
 * Constructs an empty list with the specified initial capacity.
 */
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);
    }
}

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * Constructs a list containing the elements of the specified
 * collection, in the order they are returned by the collection's
 * iterator.
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[]
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
      

ArrayList 類提供了三個構造方法,如上所示。除了初始化一個空的ArrayList以外,還支援使用另外一個容器中的元素來初始化ArrayList。注意到,在初始化一個空的ArrayList時,如果不指定容量的大小,預設容量是10。在初始化一個空的ArrayList時,如果指定容量為0,則數組引用指向的是一個靜态成員變量EMPTY_ELEMENTDATA;如果使用預設容量,則數組引用指向的是一個靜态成員變量DEFAULTCAPACITY_EMPTY_ELEMENTDATA;除此以外,按照實際指定的容量配置設定數組空間。

擴容

ArrayList既然是基于可變數組的,那麼在底層數組的存儲容量不足時肯定會進行擴容操作,以改變容器的容量。擴容的操作是通過下面的代碼進行實作的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
      
/**
 * Increases the capacity of this <tt>ArrayList</tt> instance, if
 * necessary, to ensure that it can hold at least the number of elements
 * specified by the minimum capacity argument.
 */
public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // any size if not default element table
        ? 0
        // larger than default for default empty table. It's already
        // supposed to be at default size.
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    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;
}

public int size() {
    return size;
}

public boolean isEmpty() {
    return size == 0;
}
      

這一段代碼的注釋很清楚了,大緻解釋一下:

ensureCapacity

方法可供外部調用,而

ensureCapacityInternal

則僅供内部調用,都是要確定目前容器能夠容納給定數量的元素,它們都會調用

ensureExplicitCapacity

方法;在每次調用

ensureExplicitCapacity

方法時,會将

modCount

 的值加1,表明 ArrayList 發生了結構化的修改,然後根據目前數組能容納的元素數量來決定是否需要調用

grow

方法來調整數組的大小;

grow

方法負責調整數組的大小,注意每次調整時将容量擴大為目前容量的1.5倍(

oldCapacity + (oldCapacity >> 1)

),如果還是不能滿足容量要求,就按照所需的最小容量來配置設定,然後将原數組中的元素複制到新數組中。ArrayList 能夠支援的最大容量為 int 值的上限,超過會報

OutOfMemoryError

異常。

這裡有一個奇怪的地方在于,modCount 的值會在 

ensureExplicitCapacity

 方法中加1。前面已經說過,modCount用來記錄容器發生結構化修改的次數,按道理來說實在加入或移除元素是才會修改的,為什麼會在這裡調用呢。後面我們會看到,每次新加入元素時,

ensureExplicitCapacity

 都會被調用,因而可以将modCount的修改放在此方法中,就不必在 

add

 及 

addAll

 方法中進行修改了。

添加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
      
/**
 * Appends the specified element to the end of this list.
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

/**
 * Inserts the specified element at the specified position in this
 * list. Shifts the element currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices).
 */
public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

/**
 * Appends all of the elements in the specified collection to the end of
 * this list, in the order that they are returned by the
 * specified collection's Iterator.  The behavior of this operation is
 * undefined if the specified collection is modified while the operation
 * is in progress.  (This implies that the behavior of this call is
 * undefined if the specified collection is this list, and this
 * list is nonempty.)
 */
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

/**
 * Inserts all of the elements in the specified collection into this
 * list, starting at the specified position.  Shifts the element
 * currently at that position (if any) and any subsequent elements to
 * the right (increases their indices).  The new elements will appear
 * in the list in the order that they are returned by the
 * specified collection's iterator.
 */
public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);

    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount

    int numMoved = size - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);

    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

/**
 * Checks if the given index is in range.  If not, throws an appropriate
 * runtime exception.  This method does *not* check if the index is
 * negative: It is always used immediately prior to an array access,
 * which throws an ArrayIndexOutOfBoundsException if index is negative.
 */
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

/**
 * A version of rangeCheck used by add and addAll.
 */
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
      

可以向ArrayList容器中添加單個元素,也可以添加一個容器;預設添加到數組的末尾,也可以添加到指定位置。首先會确認目前容量是否充裕,如果不足則會進行擴容操作。每次添加元素時都會修改modCount的值,前面已經詳細地說明過了。在指定添加的位置時,會先檢查指定的位置是否合理,不合理則會抛出

IndexOutOfBoundsException

;如果插入位置合理,則會将相應位置後面的元素向後挪以騰出空間,然後将待添加的元素放入。

移除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
      
/**
 * Removes the element at the specified position in this list.
 * Shifts any subsequent elements to the left (subtracts one from their
 * indices).
 */
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);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

/**
 * Removes the first occurrence of the specified element from this list,
 * if it is present.  If the list does not contain the element, it is
 * unchanged.  More formally, removes the element with the lowest index
 */
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 remove method that skips bounds checking and does not
 * return the value removed.
 */
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
}

/**
 * Removes all of the elements from this list.  The list will
 * be empty after this call returns.
 */
public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

/**
 * Removes from this list all of the elements whose index is between
 * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
 * Shifts any succeeding elements to the left (reduces their index).
 * This call shortens the list by {@code (toIndex - fromIndex)} elements.
 * (If {@code toIndex==fromIndex}, this operation has no effect.)
 */
protected void removeRange(int fromIndex, int toIndex) {
    modCount++;
    int numMoved = size - toIndex;
    System.arraycopy(elementData, toIndex, elementData, fromIndex,
                     numMoved);

    // clear to let GC do its work
    int newSize = size - (toIndex-fromIndex);
    for (int i = newSize; i < size; i++) {
        elementData[i] = null;
    }
    size = newSize;
}
      

移除元素時其實就是使用

System.arraycopy

将移除後仍保留的元素複制到正确的位置上,并調整目前的size大小。注意,在元素移動完成後,要顯式地将數組中不再使用的位置中存放的值賦為null,進而確定GC能夠正常地回收資源。

下面再看看如何做到從ArrayList中移除指定容器内的元素以及保留指定容器中的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
      
/**
 * Removes from this list all of its elements that are contained in the
 * specified collection.
 */
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}

/**
 * Retains only the elements in this list that are contained in the
 * specified collection.  In other words, removes from this list all
 * of its elements that are not contained in the specified collection.
 */
public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, true);
}

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++)
            //1) 移除c中元素,complement == false
            //   若elementData[r]不在c中,則保留
            //2)保留c中元素,complement == true
            //   若elementData[r]在c中,則保留
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        // 1)r == size, 則操作成功了
        // 2)r != size, c.contains抛出了異常,
        //      可能是因為元素和c中元素類型不相容,或者c不支援null元素
        //      則将後面尚未檢查的元素向前複制
        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;
}
      

我們可以看到,核心的方法在于

batchRemove(Collection<?> c, boolean complement)

,無論是移除給定容器中的元素

removeAll(Collection<?> c)

還是隻保留指定容器中的元素

retainAll(Collection<?> c)

都是通過該方法來實作的。該方法通過傳入的一個布爾類型确定ArrayList中每個元素是否應該保留,詳細的注釋參見上面代碼中的中文注釋。

上面從ArrayList中移除元素的所有方法中都沒有對移除元素後的數組大小進行調整,這種情況下可能會在移除大量元素後造成空間的浪費。這時候可以通過

trimToSize

方法将數組大小調整為實際的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
      
/**
 * Trims the capacity of this ArrayList instance to be the
 * list's current size.  An application can use this operation to minimize
 * the storage of an ArrayList instance.
 */
public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}
      

更新及查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
      
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

/**
 * Returns the index of the first occurrence of the specified element
 * in this list, or -1 if this list does not contain the element.
 * More formally, returns the lowest index <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
 * or -1 if there is no such index.
 */
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;
}

/**
 * Returns the index of the last occurrence of the specified element
 * in this list, or -1 if this list does not contain the element.
 * More formally, returns the highest index <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
 * or -1 if there is no such index.
 */
public int lastIndexOf(Object o) {
    if (o == null) {
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

/**
 * Returns the element at the specified position in this list.
 */
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

/**
 * Replaces the element at the specified position in this list with
 * the specified element.
 */
public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
      

基于數組的實作使得更新元素及查找元素變得比較簡單。在set方法中不會修改modCount的值。

疊代

在AbstractList中其實已經提供了疊代器的一個實作,ArrayList類中又提供了一個優化後的實作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
      
/**
 * An optimized version of AbstractList.Itr
 */
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}
      

疊代器中通過一個遊标cursor來達到周遊所有元素的目的,同時還保留了上一個通路的位置以便于remove方法的實作。前面說過,ArrayList的實作并不是線程安全,其fail-fast機制的實作是通過modCount變量來實作的。在這裡我們可以清楚地看到,在疊代器的next和remove等方法中,首先就會調用

checkForComodification

方法來判斷ArrayList容器是否在疊代器建立後發生過結構上的修改,其具體的實作是通過比較建立疊代器時的modCount(即expectedModCount)和目前modCount是否相同來完成的。如果不相同,表明在此過程中其他線程修改了ArrayList(添加了或移除了元素),會抛出

ConcurrentModificationException

List接口還支援另一種疊代器,

ListIterator<E>

,不僅可以使用next()方法向前疊代,還可以使用previous()方法向後移動遊标。ArrayList中也實作了

listIterator()

listIterator(int index)

方法,比較簡單,這裡就不再詳細說了。

子清單

所謂的子清單,就是清單中指定範圍内的一些元素,通過調用

subList(int fromIndex, int toIndex)

來擷取。對子清單的操作會影響到父清單。通過子清單可以達到操作父清單中部分元素的目的,如隻疊代部分範圍内的元素,或者隻對部分範圍内的元素進行排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
      
private class SubList extends AbstractList<E> implements RandomAccess {
    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;

    SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
        this.parent = parent;
        this.parentOffset = fromIndex;
        this.offset = offset + fromIndex;
        this.size = toIndex - fromIndex;
        this.modCount = ArrayList.this.modCount;
    }

    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);
        int cSize = c.size();
        if (cSize==0)
            return false;

        checkForComodification();
        parent.addAll(parentOffset + index, c);
        this.modCount = parent.modCount;
        this.size += cSize;
        return true;
    }

    private void checkForComodification() {
        if (ArrayList.this.modCount != this.modCount)
            throw new ConcurrentModificationException();
    }
}
      

上面列出了ArrayList中使用的子清單的部分代碼,SubList繼承了AbstractList,并實作了RandomAccess接口。SubList中并沒有向ArrayList那樣有一個數組來存放元素,而是持有了父清單的引用,并儲存了元素相對于父清單的偏移及範圍等資訊。對子清單的所有操作都是通過父清單來完成的。值得說明的是,因為SubList也是AbstractList的子類,因而也有一個modCount字段。在建立子清單時,modCount和父清單一緻;以後每當通過子清單修改父清單時也都會保持一緻。在調用子清單的方法時,類似于疊代器,首先也會通過

checkForComodification

方法確定父清單的結構沒有發生改變,否則會抛出

ConcurrentModificationException

序列化

前面提到過數組 elementData 是使用 transient 來修飾的,這個其實就和序列化及反序列化相關。transient 是一個關鍵字,用 transient 修飾的變量不再是對象持久化的一部分,即預設序列化機制中該變量不用被序列化。

這一點可能讓人很費解,如果不用被序列化,那麼反序列化的時候不是就丢失了存儲的資料了嗎?實際上,在 ArrayList 中對序列化和反序列化過程進行了更細緻的控制,即通過 

writeObject()

和 

readObject()

 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
      
/**
 * Save the state of the <tt>ArrayList</tt> instance to a stream (that
 * is, serialize it).
 *
 * @serialData The length of the array backing the <tt>ArrayList</tt>
 *             instance is emitted (int), followed by all of its elements
 *             (each an <tt>Object</tt>) in the proper order.
 */
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

/**
 * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
 * deserialize it).
 */
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;

    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in capacity
    s.readInt(); // ignored

    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}
      

可見,在序列化時并不是将整個數組全部寫入輸出流中,因為數組通常都不是處于完全填充的狀态,對于為 null 的元素就不必儲存,也可以達到節約空間的目的。後面我們會看到很多集合類中都采取了這種方式進行序列化和反序列化。

小結

本文通過源碼分析了Java 8 集合架構中ArrayList的實作方式。ArrayList内部是通過數組進行實作的,具有高效的随機通路的特性;但插入和删除元素時往往需要複制數組,開銷較大。在容器建立完成後需要進行大量通路,但插入和删除操作使用較少的情況下比較适合使用ArrayList。

熬夜不易,點選請老王喝杯烈酒!!!!!!!