ArrayList source code analysis

1. ArrayList inheritance and implementation structure
ArrayList inherits the abstract list class AbstractList and implements the list interface and Serializable interface.

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

2. What is the underlying implementation structure of ArrayList? What is the default initialization capacity?
A: the bottom layer of ArrayList is implemented by array, and when the initialization capacity is not specified, the default initialization capacity is 10.

private static final int DEFAULT_CAPACITY = 10;  //The default initialization capacity is set to 10
transient Object[] elementData;   //The bottom layer uses object array to store elements

3. Underlying constructor
(1) Empty parameter constructor: the underlying array variable elementData is initialized to an empty array by default. Only when we add data to the list for the first time, will we re assign elementData to an array with a length of 10. This process is still complex. For details, see the following explanation (a series of operations experienced when adding elements to ArrayList for the first time).

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;  
        //Here, the elementData array is initialized as an empty array, rather than directly as an array with a length of 10. This process is only carried out when adding elements for the first time
    }

(2) The constructor with parameters converts a collection type variable c passed in by parameters into an array and adds it to the array at the bottom of the list.

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

4. The series of operations that ArrayList goes through when it first adds elements.
(1) When we call the parameterless constructor to create a list object, the underlying layer will initialize the elementData array to an empty array, DefaultAttribute_ EMPTY_ elementData(private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};);
(2) When we first call the add() method to add elements to the list (list.add("tmj");), Enter the body of the add() method;

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Ensure the capacity of the array. If it is added for the first time, the elementData at this time is still an empty array
        //You need to assign elementData to an array object with a length of 10 by default. If it is not added for the first time, you need to judge the array capacity and check whether the current elementData array has any remaining places to store,
        //If the capacity is insufficient, it needs to be expanded, and then assign the expanded array to the elementData variable to complete the expansion of the array
        elementData[size++] = e;  //After the capacity guarantee is completed, put the element into the index of the record, which is the index corresponding to size.
        return true;
    }

(3) ensureCapacityInternal() method call;

private void ensureCapacityInternal(int minCapacity) {  //When the add method is called for the first time, minCapacity is 1, which means that the minimum capacity of the underlying array is minCapacity, because the element must be put down
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {  //The first time you add, you enter this code block
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);  //minCapacity=1 when you first come in, so compared with the default capacity of 10, the final minCapacity is larger, which = 10
        }

        ensureExplicitCapacity(minCapacity);  //This method is used to judge whether the array needs to be expanded
    }

(4) ensureExplicitCapacity() method call

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)  //If the minimum capacity we need is less than the capacity of the current array (when we add for the first time, the current array capacity is 0), we call grow() to expand the capacity of the array
            grow(minCapacity);
    }

(5) Growth() method call

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;  //Capacity of the original array
        int newCapacity = oldCapacity + (oldCapacity >> 1);  //The new capacity of the array after capacity expansion, newCapacity = oldCapacity + oldCapacity /2;
        //Here is the key point. It can be seen that each time the array is expanded, the new array is expanded to 1.5 times the capacity of the original array. The right shift here represents the Division 2 operation.
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)  //If the new capacity is greater than the defined maximum capacity, the new capacity is assigned as the maximum capacity
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);  //Complete the transfer of elements in the array
    }

5. Introduction to common methods
(1) toArray(): convert a list object into a corresponding array object
(2) get(int index): returns the element at the specified index position

public E get(int index) {
        rangeCheck(index);  //Index range check

        return elementData(index);
    }

(3) set(int index, E element): set the element at an index position as a new element and return the value of the original element.

public E set(int index, E element) {
        rangeCheck(index);  //Index check

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

(4) Add (E): add an element to the list
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
(5) add(int index, E element): add an element at the specified index position. This operation will cause other subsequent elements of the index to move back in turn, freeing up a space for new elements.

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

(6) remove(int index): delete the elements at the specified index position. This operation will cause the elements behind the index to move to the front in turn, and the number of elements in the size –, list will be reduced by 1.

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

(7) remove(Object o): remove the first specific object o found from the list. If there are multiple identical objects, only the first one will be removed, and return whether to remove the object. If there is no such object in the list, return false, otherwise return true. If you need to remove multiple, you can write a loop to iterate and delete.

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

(8) clear(): clearing the elements in this list is actually setting the value at each position of the underlying array to null and the size to 0, which can help GC garbage collection.

public void clear() {
        modCount++;

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

        size = 0;
    }

(9) Remove range (int fromindex, int toindex): remove all elements from one index1 to another index2 in the list.

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

Keywords: Java data structure set

Added by ljzxtww on Mon, 07 Feb 2022 21:39:11 +0200