ArrayList implementation principle analysis

ArrayList overview:
ArrayList is a mutable array implementation of the List interface. Implements all optional list operations and allows all elements including null. In addition to implementing the List interface, this class provides methods for manipulating the size of the array used internally to store the list.
Each ArrayList instance has a capacity, which is the size of the array used to store the list elements. It is always at least equal to the size of the list. As elements are added to the ArrayList, its capacity also grows automatically. Automatic growth will result in a new copy of the data to the new array, so if you can predict the amount of data, you can specify its capacity when constructing the ArrayList. Applications can also use the ensureCapacity operation to increase the capacity of an ArrayList instance before adding a large number of elements, which can reduce the number of incremental reallocations.
Note that this implementation is not synchronous. If multiple threads simultaneously access an ArrayList instance and at least one of them structurally modifies the list, it must maintain external synchronization.

Implementation of ArrayList:
For ArrayList, it implements the List interface, and the bottom layer uses an array to store all elements. Its operations are basically operations on arrays. Let's analyze the source code of ArrayList:
1) The bottom layer is implemented using an array:

private transient Object[] elementData;

2) Construction method:
ArrayList provides three constructors, which can construct an empty list with a default initial capacity of 10, construct an empty list with a specified initial capacity, and construct a list containing the elements of the specified collection. Collection's iterators return their order.

public ArrayList() {
    this(10);
}

public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    this.elementData = new Object[initialCapacity];
}

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    size = elementData.length;
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}

3) Storage:
ArrayList provides set(int index, E element), add(E e), add(int index, E element), addAll(Collection

// 用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。
public E set(int index, E element) {
    RangeCheck(index);

    E oldValue = (E) elementData[index];
    elementData[index] = element;
    return oldValue;
}
// 将指定的元素添加到此列表的尾部。
public boolean add(E e) {
    ensureCapacity(size + 1); 
    elementData[size++] = e;
    return true;
}
// 将指定的元素插入此列表中的指定位置。
// 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。
public void add(int index, E element) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
    // 如果数组长度不足,将进行扩容。
    ensureCapacity(size+1);  // Increments modCount!!
    // 将 elementData中从Index位置开始、长度为size-index的元素,
    // 拷贝到从下标为index+1位置开始的新的elementData数组中。
    // 即将当前位于该位置的元素以及所有后续元素右移一个位置。
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}
// 按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的尾部。
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacity(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}
// 从指定的位置开始,将指定collection中的所有元素插入到此列表中。
public boolean addAll(int index, Collection<? extends E> c) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(
            "Index: " + index + ", Size: " + size);

    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacity(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;
}

4) Read:

// 返回此列表中指定位置上的元素。
public E get(int index) {
    RangeCheck(index);

    return (E) elementData[index];
}

5) Delete:
ArrayList provides the delete function according to the subscript or the specified object. as follows:

// 移除此列表中指定位置上的元素。
public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // Let gc do its work

    return oldValue;
}
// 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。
public boolean remove(Object o) {
    // 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                // 类似remove(int index),移除列表中指定位置上的元素。
                fastRemove(index);
                return true;
            }
} else {
    for (int index = 0; index < size; index++)
        if (o.equals(elementData[index])) {
            fastRemove(index);
            return true;
        }
    }
    return false;
}

Note: Removing an element from an array also causes all elements after the removed element to move one position to the left.
6) Adjust the capacity of the array:
From the code for storing elements in the ArrayList described above, we can see that whenever an element is added to the array, it is necessary to check whether the number of added elements exceeds the length of the current array. If it exceeds, the array will be expanded to meet the needs of adding data. Array expansion is achieved through a public method ensureCapacity(int minCapacity). I can also use ensureCapacity to manually increase the capacity of the ArrayList instance before actually adding a lot of elements to reduce the number of incremental reallocations.

public void ensureCapacity(int minCapacity) {
    modCount++;
    int oldCapacity = elementData.length;
    if (minCapacity > oldCapacity) {
        Object oldData[] = elementData;
        int newCapacity = (oldCapacity * 3)/2 + 1;
            if (newCapacity < minCapacity)
                newCapacity = minCapacity;
      // minCapacity is usually close to size, so this is a win:
      elementData = Arrays.copyOf(elementData, newCapacity);
    }
}

As can be seen from the above code, when the array is expanded, the elements in the old array will be copied to the new array, and the increase of the array capacity each time is about 1.5 times its original capacity. The cost of this operation is very high, so in actual use, we should try to avoid the expansion of the array capacity. When we can predict the number of elements to be saved, we need to specify its capacity when constructing an ArrayList instance to avoid the occurrence of array expansion. Or according to actual needs, manually increase the capacity of the ArrayList instance by calling the ensureCapacity method.
ArrayList also gives us the ability to adjust the capacity of the underlying array to the size of the actual elements that the current list holds. It can be achieved through the trimToSize method. code show as below:

public void trimToSize() {
    modCount++;
    int oldCapacity = elementData.length;
    if (size < oldCapacity) {
        elementData = Arrays.copyOf(elementData, size);
    }
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325615023&siteId=291194637