This is probably the most detailed explanation of ArrayList!

Hand tear ArrayList source code

The article was first published in GitHub open source project: Java supernatural Road

Introduction to ArrayList

ArrayList is an array list. Its main underlying implementation is the Object array, but compared with the array in Java, its capacity can change dynamically and can be regarded as a dynamic array structure. In particular, when we load basic types of data int, long, boolean, short, byte... We can only store their corresponding wrapper classes.

ArrayList features

  • Elements are ordered and repeatable

  • The speed of adding and deleting elements is slow. Each time you add and delete elements, you need to change the array length, copy elements and move element positions. Therefore, the speed of adding and deleting elements will be relatively slow.

  • The speed of querying elements is fast, because the underlying data structure is implemented based on Object array. The array is a continuous space in memory, so you can quickly obtain the elements at the corresponding position according to the address + index.

  • Thread unsafe

  • Implement the Serializable markup interface. ArrayList implements the tagging interface to provide serialization and deserialization functions for classes, which means that ArrayList supports serialization and can be transmitted through serialization.

  • Implement clonable markup interface. Class to use the clone method, it must implement the clonable interface, which provides the cloning function.

  • Implements the RandomAccess markup interface. It provides random access function for ArrayList, that is, the function of obtaining element objects through subscripts.

  • Implement the List interface, which is one of the implementation classes of List.

  • To implement the Iterable interface, you can use for each iteration.

First, take a look at the inheritance structure diagram of the collection

Source code analysis

ArrayList related member variables

In the following analysis, relevant member variables will be called. In order to facilitate the analysis of the core source code, it will be explained in advance.

     * Default initialization capacity
    private static final int DEFAULT_CAPACITY = 10;

     * Shared empty array instances for empty instances are used to optimize the generation of unnecessary empty arrays when creating empty ArrayList instances,
     * Make all empty instances of ArrayList point to the same empty array.
    private static final Object[] EMPTY_ELEMENTDATA = {};

     * Shared empty array instance for empty instances of default size.
     * We compare it with EMPTY_ELEMENTDATA is separated to understand how much capacity to expand when adding the first element.
     * This is to ensure that the minimum capacity of the instance created by the parameterless composition function is the default size of 10 when adding the first element.
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

     * Set the container that really stores data, and store the array buffer of ArrayList elements. The capacity of ArrayList is the length of the array buffer.
     * When the first element is added, elementdata = = defaultcapability_ EMPTY_ Elementdata will
     * Extended to DEFAULT_CAPACITY (default capacity)
    transient Object[] elementData;

     * ArrayList Size of (number of elements it contains)
    private int size;

ArrayList construction method

There are three construction methods for ArrayList: parameterless construction method, construction method that specifies the initial capacity value, construction method that contains the specified set element list, and return them in the order of the iterator of the set.

Default null parameter construction method

I have read some blogs on the Internet. When introducing the ArrayList null parameter construction method, I usually write, "when calling the null parameter constructor, an array with a capacity of 10 will be created". To verify this statement, we can look at the ArrayList source code based on jdk 1.8(201):

    public ArrayList() {

We can see that the null parameter method just adds DefaultAttribute_ EMPTY_ The empty array of elementData is assigned to elementData and does not specify an action with an initial capacity of 10. So why is this statement? The reason is that in jdk 1.2 ~ jdk 1.6, ArrayList will indeed generate an empty array with a specified underlying data structure capacity of 10 through the null parameter construction method.

After jdk 1.7, in order to avoid useless memory occupation, the null parameter construction method of ArrayList only creates an empty array with the length of the underlying data structure of 0** The capacity is expanded to 10 only when the element is added for the first time** The specific expansion process will be analyzed in more detail below.

Generates a method with a specified initial capacity value

When you know how many elements to add to the ArrayList, you should give priority to using this construction method, which can save the resources consumed by the expansion of ArrayList.

     * Constructs an empty list with the specified initial capacity.
     * @param  initialCapacity Specified initial capacity
     * @throws If the specified initial capacity is negative, an IllegalArgumentException is thrown
    public ArrayList(int initialCapacity) {
        // Judge whether the specified initial capacity is greater than 0
        if (initialCapacity > 0) {
            // Generate an array with the specified capacity and assign it to elementData
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            // If the specified capacity is 0, empty will be selected_ Assigning the elementData address to elementData is equivalent to creating an empty array
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+

Constructs a list containing the elements of the specified collection, in the order in which the iterator of the collection returns them

     * Constructs a list containing the elements of the specified collection, the order of which is returned by the collection iterator.
     * @param c Put its elements into the collection of this list
     * @throws NullPointerException If the specified collection is null
    public ArrayList(Collection<? extends E> c) {
        // Convert the collection parameters in the constructor to an array
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray may (incorrect) not return Object []
            // jdk bug (the behavior of the toArray() method of ArrayList implemented in Arrays is inconsistent with the specification) has been fixed for 15 years< >
            // Judge again
            if (elementData.getClass() != Object[].class)
                // Creating and copying arrays
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // Assign the address of the empty array to the array of collection storage elements
            this.elementData = EMPTY_ELEMENTDATA;

Under what circumstances can c.toArray() possibly (incorrectly) not return Object []? See the following program example.

    public static void main(String[] args) {

        List list = new ArrayList(Arrays.asList("small", "min"));
        // class java.util.ArrayList
        Object[] object = list.toArray();
        // class [Ljava.lang.Object;

        List asList = Arrays.asList("small", "min");
        // class java.util.Arrays$ArrayList
        Object[] objects = asList.toArray();
        // class [Ljava.lang.String;

By running the program, we can know that the java.util.ArrayList.toArray() method will return Object [], no problem. The toArray() method of the private inner class ArrayList of java.util.Arrays may not return Object []

Why does this happen? Let's look at the source code of the toArray() method of ArrayList:

public Object[] toArray() {
    return Arrays.copyOf(elementData, size);

The Arrays.copyOf() method is used:

public static <T> T[] copyOf(T[] original, int newLength) {
    // original.getClass() is a class [Ljava.lang.Object]
    return (T[]) copyOf(original, newLength, original.getClass());

Specific implementation of copyOf():

public static <T,U> T[] copyOf(U[] original,
          int newLength, Class<? extends T[]> newType) {
     * If the newType is Object[] copy, the array type is Object, otherwise it is newType.
     * A new array is created regardless of the result of the ternary operator.
     * The length of the new array must be the same as the size of the collection
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    // Copy of array
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    // Returns a new array
    return copy;

We know that elementData in ArrayList is of Object [] type, so the toArray() method of ArrayList must return Object [].

Let's take another look at the internal ArrayList source code of java.util.Arrays (intercepted part of the source code):

private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess,
    	// An array of storage elements
        private final E[] a;

        ArrayList(E[] array) {
            // Directly assign the accepted array to a
            // The Objects.requireNonNull(T object) method functions. If the passed in object is not null, it returns the modified object
            a = Objects.requireNonNull(array);

        public Object[] toArray() {
            return a.clone();

This is the source code of the Arrays.asList() method

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);

It's not hard to see that the toArray() method of the internal ArrayList of java.util.Arrays is to return whatever type of array the constructor receives.

Therefore, in this case, c.toArray() may (incorrectly) not return Object [].

Add method

Add (E) method to add an element to the end of the list

     * Appends the specified element to the end of this list.
     * @param e Elements to add to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
    public boolean add(E e) {
        // Before adding elements, first call ensureCapacityInternal to verify the internal capacity
        // Because you want to add an element, the method is size + 1
        ensureCapacityInternal(size + 1);
        // The essence of adding elements is to assign values to the last x array element
        elementData[size++] = e;
        return true;

ensureCapacityInternal() method

	private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
	// The minimum expansion capacity is obtained
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // Judge whether the array storing data in the collection is equal to the array with empty capacity. In fact, it depends on whether there is data in the array
            // The maximum value is calculated from the minimum capacity and the default capacity
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        return minCapacity;

ensureExplicitCapacity() method

    private void ensureExplicitCapacity(int minCapacity) {
        // Actual number of times to modify the set + + (it is useless in the process of capacity expansion, mainly used in the iterator)

        // Prevent spillage
        if (minCapacity - elementData.length > 0)
            // Call the grow method for capacity expansion

The above methods are summarized as follows:

  • When we add an element to the ArrayList, if the ArrayList is an empty collection without any elements, the minCapacity will change to 10 after executing the calculateCapacity() method in ensueacapacityinternal(). At this time, minCapacity - elementdata. Length > 0 is established, and the growth (minCapacity) method will be entered.
  • When add adds the second element, minCapacity is 2. At this time, elementData.length (capacity) is expanded to 10 after adding the first element. At this time, minCapacity - elementdata. Length > 0 is not valid, so it will not enter the growth (minCapacity) method.
  • If the element is added until the 11th element is added, mincapacity - elementdata.length > 0 is established (i.e. 11 - 10 > 0), enter the growth () method for capacity expansion.

Growth() expansion method

     * The maximum size of the array to allocate.
     * Some virtual machines keep some headers in the array.
     * Attempting to allocate a larger array may result in
     * OutOfMemoryError: The requested array size exceeds the VM limit
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

     * Increase the capacity to ensure that it can accommodate at least
     * @param minCapacity The number of elements specified by the minimum capacity parameter.
    private void grow(int minCapacity) {
        // Record the actual length of the original array
        int oldCapacity = elementData.length;
        // Core capacity expansion algorithm, the capacity after capacity expansion is 1.5 times of the original capacity.
        // Oldcapacity > > 1 shift operation (more efficient), the result is equal to oldCapacity / 2.
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // Check whether the capacity after expansion is greater than the minimum required capacity. If it is still less than the minimum required capacity, the minimum required capacity is regarded as the new capacity of the array
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // Then check whether the new capacity exceeds the maximum capacity defined by ArrayList,
        // If it exceeds, hugeCapacity() is called to compare minCapacity with MAX_ARRAY_SIZE
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);

     * Maximum capacity returns Integer.MAX_VALUE
	private static int hugeCapacity(int minCapacity) {
        // Overflow, throw exception
        if (minCapacity < 0)
            throw new OutOfMemoryError();
        // If minCapacity is greater than MAX_ARRAY_SIZE, the new capacity is integer.max_ VALUE,
        // Otherwise, the new capacity size is MAX_ARRAY_SIZE. 
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :

add(int index, E element) adds an element method at the specified index

     * Inserts the specified element into the specified location in this list.
     * Move the element currently in that position (if any) and any subsequent elements to the right (add one to its index), and finally size +1.
     * @param index Specifies the index into which the element is to be inserted
     * @param element Element to insert
     * @throws IndexOutOfBoundsException {@inheritDoc}
    public void add(int index, E element) {
        // Scope check

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // The arraycopy() method copies the array itself
        // elementData: source array; index: the starting position in the source array;
        // elementData: target array; index + 1: the starting position in the target array;
        // size - index: the number of array elements to copy;
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;

     * add And the rangeCheck version used by addAll.
    private void rangeCheckForAdd(int index) {
        // If it is out of range, an exception will be thrown
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

Here are some important but easily overlooked knowledge points:

  • The length attribute in java is for arrays. For example, if you declare an array, you use the length attribute to know the length of the array.
  • The length() method in java is for strings. If you want to see the length of this string, you need to use the length() method.
  • The size() method in java is for generic collections. If you want to see how many elements there are in this generic, call this method to view it!

ensureCapacity method

There is an ensureCapacity method in the source code of ArrayList. I don't know if you have noticed. This method has not been called inside ArrayList, so it is obviously provided for users to call. What is the function of this method?

    If necessary, increase the capacity of this ArrayList instance to ensure that it can accommodate at least the number of elements specified by the minimum capacity parameter.
     * @param   minCapacity   Minimum capacity required
    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) {

It is best to use the ensureCapacity method before add ing a large number of elements to reduce the number of incremental reassignments

Contact with other containers

ArrayList and Vector

Same point

  • Both implement the java.util.List interface.
  • The underlying data structures are implemented with arrays.
  • When you first add an element, the default initial capacity is 10.


  • Vector is thread safe and ArrayList is non thread safe.

  • Vector does not support serialization. ArrayList implements the Serializable interface and supports serialization.

  • The expansion of Vector is related to the expansion factor. If the capacity expansion factor is specified and the factor is greater than 0, when capacity expansion is required, the new capacity is the value of the old capacity + capacity expansion factor.

    If the expansion factor is not specified, the capacity of the Vector after expansion is twice the old capacity by default.

    import java.util.Vector;
    public class VectorTest {
        public static void main(String[] args) {
             * The specified initial capacity is 2 and the expansion factor is 5
            Vector list = new Vector(2, 5);
            System.out.println("Capacity of container before expansion:" + list.capacity());
            System.out.println("Capacity of container after expansion:" + list.capacity());
            // output
            // Capacity of container before expansion: 2
            // Capacity of container after capacity expansion: 7
             * If not specified, the default initial capacity is 10, and the capacity after expansion is twice the capacity of the original container
            Vector list2 = new Vector();
            System.out.println("Capacity of container before expansion:" + list2.capacity());
            for (int i = 0; i < 8; i++) {
                list2.add("Fill default capacity");
            System.out.println("Capacity of container after expansion:" + list2.capacity());
            // output
            // Capacity of container before expansion: 10
            // Capacity of container after capacity expansion: 20

    What is the difference between Arraylist and LinkedList

    1. Whether thread safety is guaranteed: ArrayList and LinkedList are not synchronized, that is, thread safety is not guaranteed;
    2. Underlying data structure: Arraylist uses an Object array at the bottom; The underlying layer of the LinkedList uses a two-way linked list data structure (before JDK1.6, it was a circular linked list, and JDK1.7 cancels the cycle. Note the differences between the two-way linked list and the two-way circular linked list, which are described below!)
    3. Whether insertion and deletion are affected by element location: ① ArrayList is stored in array, so the time complexity of inserting and deleting elements is affected by element location. For example, when the add (E) method is executed, the ArrayList will append the specified element to the end of the list by default. In this case, the time complexity is O(1). However, if you want to insert and delete elements at the specified position I, the time complexity of (add(int index, E element)) is O(n-i). Because when performing the above operations, the (n-i) elements after the I and I elements in the set must perform the operation of moving backward / forward one bit. ② LinkedList is stored in a linked list. Therefore, for the insertion of the add (E, e) method, the time complexity of deleting elements is not affected by the element position, which is approximately O(1). If you want to insert and delete elements at the specified position I ((add(int index, E element)) time complexity is approximately o(n)), because you need to move to the specified position before inserting.
    4. Whether it supports fast random access: LinkedList does not support efficient random element access, while ArrayList does. Fast random access is to quickly obtain the element object (corresponding to the get(int index) method) through the element sequence number.
    5. Memory space occupation: the space waste of ArrayList is mainly reflected in that a certain capacity space is reserved at the end of the list, while the space cost of LinkedList is reflected in that each element needs to consume more space than ArrayList (because it needs to store direct successors, direct precursors and data).

    Is the addition and deletion of ArrayList necessarily slower than that of LinkedList


    Remove Method

    Removes the specified subscript element method

     * Removes the element in the list at the specified subscript position
     * Move all subsequent elements to the left
     * @param The specified subscript to remove
     * @return Returns the removed element
     * @throws IndexOutOfBoundsException will be thrown if the subscript is out of bounds
    public E remove(int index) {
        E oldValue = elementData(index);
        int numMoved = size - index - 1;
        if (numMoved > 0)
                        index+1, elementData, index,  numMoved);
        // Leave the reference blank for GC to recycle
        elementData[--size] = null;
        return oldValue;

    Remove specified element method

     * Removes the first specified element that appears in the list
     * If it exists, remove returns true
     * Otherwise, false is returned
     * @param o Specify element
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    return true;
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    return true;
        return false;

    The name of the removed method and the number of parameters are the same, which should be paid attention to when using.

    Private removal method

     * Private remove methods skip boundary checking and do not return removed elements
    private void fastRemove(int index) {
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
        // Leave the reference blank for GC to recycle
        elementData[--size] = null;

    Search method

    Finds the location of the specified element

     * Returns the subscript of the first occurrence of the specified element
     * If the element does not exist, - 1 is returned
     * If o ==null, special processing will be performed
    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;

    Finds the element at the specified location

     * Returns the element at the specified location
     * @param  index Specifies the location of the element
     * @throws index An IndexOutOfBoundsException will be thrown if the boundary is exceeded
    public E get(int index) {
        return elementData(index);

    This method directly returns the elements of the specified subscript of the elementData array, which is still very efficient. Therefore, the traversal efficiency of ArrayList and for loops is also very high.

    Serialization method

     * Save the state of the ArrayLisy instance to a stream
    private void writeObject( s)
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        // Write out size as capacity for behavioural compatibility with clone()
        // Write all elements in order
        for (int i=0; i<size; i++) {
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();

    Deserialization method

     * Regenerate an ArrayList based on a stream (parameter)
    private void readObject( s)
        throws, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;
        // Read in size, and any hidden stuff
        // Read in capacity
        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();

    After reading the serialization and deserialization methods, we can finally answer the second question at the beginning. The reason why elementData is decorated with transient is that JDK does not want to serialize or deserialize the entire elementData, but just serialize or deserialize the size and the actually stored elements, thus saving space and time.

    Create subarray

    public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);

    Let's take a look at the short version of SubList:

    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 E set(int index, E e) {
            E oldValue = ArrayList.this.elementData(offset + index);
            ArrayList.this.elementData[offset + index] = e;
            return oldValue;
        // Omit code
    • The set() method of SubList directly modifies the elementData array in ArrayList, which should be noted in use
    • SubList does not implement the Serializable interface and cannot be serialized


    Create iterator method

    public Iterator<E> iterator() {
        return new Itr();

    Itr attribute

    // The subscript of the next element to return
    int cursor;
    // The subscript of the last element to return. No element returns - 1
    int lastRet = -1;
    // Expected modCount
    int expectedModCount = modCount;

    The hasNext() method of Itr

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

    Itr's next() method

    public E next() {
        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];
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();

    During iteration, it will check whether modCount is equal to expectedModCount. If not, the famous ConcurrentModificationException will be thrown. When will concurrent modificationexception be thrown?

    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        for (int i = 0; i < 10; i++) {
    public static void remove(ArrayList<Integer> list) {
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            Integer number =;
            if (number % 2 == 0) {
                // Throw ConcurrentModificationException

    How can I write so as not to throw a ConcurrentModificationException? Very simple, set list.remove(number); Replace with iterator.remove(); Just. why? Please see Itr's remove () source code

    Itr's remove() method

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        try {
            cursor = lastRet;
            lastRet = -1;
            // After removal, reassign modCount to expectedModCount
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();

    The reason is that Itr's remove() method reassigns modCount to expectedModCount after removal. This is the source code. No matter single thread or multi thread, as long as the rules are violated, exceptions will be thrown.

I am an open source gentleman, pay attention to a wave, and will bring more source code analysis in the future. Let's make progress on the road of java learning!

The article was first published in GitHub open source project: Java supernatural Road More Java related knowledge, welcome to visit!

Tags: Java data structure source code analysis arraylist

Posted on Sun, 12 Sep 2021 01:35:43 -0400 by DF7