Analysis of the Source+Expansion Mechanism of the Saintest Array List

1. Introduction to ArrayList

At the bottom of the ArrayList is an array queue, which is equivalent to a dynamic array. Compared to arrays in Java, its capacity can grow dynamically. Applications can use the ensureCapacity operation to increase the capacity of ArrayList instances before adding a large number of elements. This reduces the number of incremental redistributions.

ArrayList inherits from AbstractList and implements List, RandomAccess, Cloneable, java.io.Serializable interfaces.

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

  }
  • RandomAccess is a flag interface indicating that the List collection implementing this interface supports fast random access. In ArrayList, we can quickly get an element object by its ordinal number, which is fast random access.
  • ArrayList implements the Cloneable interface, which overrides the function clone(), and can be cloned.
  • ArrayList implements the java.io.Serializable interface, which means that ArrayList supports serialization and can be transferred through serialization.

1.1. The difference between Arraylist and Vector?

  1. ArrayList is the main implementation class of List. The underlying layer uses Object [] storage, which is suitable for frequent lookup and thread insecurity.
  2. Vector is an old implementation class of List, which uses Object[] storage at the bottom and is thread safe.

1.2. What is the difference between Arraylist and LinkedList?

  1. Whether thread security is guaranteed or not: ArrayList and LinkedList are not synchronized, that is, thread security is not guaranteed;
  2. Bottom data structure: Arraylist uses Object arrays at the bottom; At the bottom of the LinkedList is a two-way linked list data structure (JDK1.6 was a circular linked list before, JDK1.7 canceled the loop. Note the difference between a two-way and a two-way circular linked list, described below!)
  3. Whether insertion and deletion are affected by element location:1 ArrayList is stored in arrays, 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 appends the specified element to the end of the list by default, in which case the time complexity is O(1). But if you want to insert and delete elements at the specified location I (add(int index, E element)), the time complexity is O(n-i). This is because the first (n-i) elements i n the set after the first and second elements perform a backward/forward move by one. (2) LinkedList is stored i n a chain table, so for the insertion of the add (E) method, the time complexity of deleting an element is not affected by the location of the element, approximating O(1), if the element is to be inserted and deleted at the specified location I (add(int index, E element)) the time complexity is approximated O (n) because it needs to be moved to the specified location before insertion.
  4. Supports fast random access: LinkedList does not support efficient random element access, while ArrayList does. Quick random access is a quick way to get an element object (corresponding to the get(int index) method) by its ordinal number.
  5. Memory space consumption: ArrayList's space waste is mainly reflected in reserving a certain amount of space at the end of the list list list, while LinkedList's space consumption is reflected in the fact that each element of the list consumes more space than ArrayList (because it stores direct successors, direct precursors, and data).

2. ArrayList Core Source Interpretation

package java.util;

import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;


public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity size
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * An empty array (for empty instances).
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

     //Shared empty array instance for default size empty instances.
      //We take it from EMPTY_ The ELEMENTDATA array is distinguished to know how much capacity needs to be increased when the first element is added.
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * Array holding ArrayList data
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList Number of elements included
     */
    private int size;

    /**
     * Constructor with initial capacity parameters (users can specify the initial size of the collection themselves when creating ArrayList objects)
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //If the parameter passed in is greater than 0, create an array of the size initialCapacity
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //Create an empty array if the parameter passed in equals 0
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            //In other cases, throw an exception
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     *Default parameterless constructor
     *DEFAULTCAPACITY_EMPTY_ELEMENTDATA 0.Initialize to 10, that is, initially an empty array. The capacity of the array becomes 10 when the first element is added
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * Constructs a list of elements containing the specified set in the order in which they are returned by the iterator of the set.
     */
    public ArrayList(Collection<? extends E> c) {
        //Converts the specified set to an array
        elementData = c.toArray();
        //If the length of the elementData array is not zero
        if ((size = elementData.length) != 0) {
            // If elementData is not of Object type (c.toArray may return an array that is not of Object type, so add the following statement to make a judgement)
            if (elementData.getClass() != Object[].class)
                //Assign the contents of an elementData array that was not originally an Object type to the elementData array of the new Object type
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // In other cases, use an empty array instead
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

    /**
     * The capacity to modify this ArrayList instance is the current size of the list. Applications can use this operation to minimize the storage of ArrayList instances.
     */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }
//Here's the extension mechanism for ArrayList
//The extension mechanism of ArrayList improves performance if only one is expanded at a time.
//Frequent insertions can result in frequent copies and degrade performance, which is avoided by the ArrayList extension mechanism.
    /**
     * If necessary, increase the capacity of this ArrayList instance to ensure that it holds at least the number of elements
     * @param   minCapacity   Minimum capacity required
     */
    public void ensureCapacity(int minCapacity) {
        //If true, minExpand has a value of 0, if false,minExpand has a value of 10
        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 the minimum capacity is greater than the existing maximum capacity
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
   //1. Obtain minimum capacity expansion
   //2. Expand capacity by minimum capacity
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              // Get the maximum value between Default Capacity and Incoming Parameters
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
  //Determine if capacity expansion is required
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            //Call the group method for expansion, which means expansion has started
            grow(minCapacity);
    }

    /**
     * Maximum array size to allocate
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * ArrayList Core approach to capacity expansion.
     */
    private void grow(int minCapacity) {
        // oldCapacity is old capacity, newCapacity is new capacity
        int oldCapacity = elementData.length;
        //Move oldCapacity one bit to the right, which is equivalent to oldCapacity/2.
        //We know that bit operations are much faster than integer division operations, and that the result of an entire sentence operation is to update the new capacity to 1.5 times the old capacity.
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //Then check if the new capacity is greater than the minimum required capacity or less than the minimum required capacity, and treat the minimum required capacity as the new capacity of the array.
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //Check again if the new capacity exceeds the maximum capacity defined by ArrayList.
        //If exceeded, hugeCapacity() is called to compare minCapacity with MAX_ARRAY_SIZE,
        //If minCapacity is greater than MAX_ARRAY_SIZE, then the new capacity is Interger.MAX_VALUE, otherwise the new capacity size is MAX_ARRAY_SIZE.
        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);
    }
    //Comparing minCapacity with MAX_ARRAY_SIZE
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

    /**
     *Returns the number of elements in this list.
     */
    public int size() {
        return size;
    }

    /**
     * Returns true if the list does not contain elements.
     */
    public boolean isEmpty() {
        //Note the difference between = and ==
        return size == 0;
    }

    /**
     * Returns true if the list contains the specified element.
     */
    public boolean contains(Object o) {
        //indexOf() method: Returns the index of the first occurrence of the specified element in this list, or -1 if the list does not contain this element
        return indexOf(o) >= 0;
    }

    /**
     *Returns the index of the first occurrence of the specified element in this list, or -1 if the list does not contain this element
     */
    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++)
                //equals() method comparison
                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 the list does not contain elements.
     */
    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 a shallow copy of this ArrayList instance. (The element itself is not copied.)
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            //The Arrays.copyOf function is to copy an array and return the copied array. Parameter is the array being copied and the length of the copy
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // This shouldn't happen because we can clone
            throw new InternalError(e);
        }
    }

    /**
     *Returns an array containing all the elements in this list in the correct order, from the first to the last element.
     *The returned array will be "safe" because the list does not retain a reference to it. (In other words, this method must assign a new array).
     *Therefore, the caller is free to modify the returned array. This method acts as a bridge between array-based and set-based API s.
     */
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    /**
     * Returns an array (from the first to the last element) containing all the elements in this list in the correct order;
     *The runtime type of the returned array is the runtime type of the specified array. Returns a list if it fits the specified array.
     *Otherwise, a new array will be assigned to the runtime type of the specified array and the size of this list.
     *If the list applies to the specified array and the rest of the space (that is, there are more lists in the array than this element), the elements in the array immediately after the end of the collection are set to null.
     *(This determines the length of the list only if the caller knows that it does not contain any empty elements.)
     */
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Create a new runtime type array, but the contents of the ArrayList array
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
            //Calling the arraycopy() method provided by System to copy between arrays
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

    // Positional Access Operations

    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

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

        return elementData(index);
    }

    /**
     * Replace the element at the specified location in this list with the specified element.
     */
    public E set(int index, E element) {
        //Boundary check on index
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        //Return elements that were originally in this location
        return oldValue;
    }

    /**
     * Appends the specified element to the end of this list.
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //Here you can see that adding elements to an ArrayList is essentially equivalent to assigning values to an array
        elementData[size++] = e;
        return true;
    }

    /**
     * Insert the specified element at the specified location in this list.
     *First call rangeCheckForAdd to check the bounds of the index; Then call the ensureCapacityInternal method to ensure that capacity is large enough;
     *Move all the members back one place after the index starts; Insert element into index position; Finally size plus 1.
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //arraycopy() is the way to copy between arrays. Here's where the arraycopy() method makes the array copy itself
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

    /**
     * Delete the element at the specified location in the list. Move any subsequent elements to the left (minus one element from its index).
     */
    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
      //Elements deleted from the list
        return oldValue;
    }

    /**
     * Removes the first occurrence (if any) of the specified element from the list. If the list does not contain this element, it will not change.
     *Returns true if the list contains the specified element
     */
    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
    }

    /**
     * Remove all elements from the list.
     */
    public void clear() {
        modCount++;

        // Set the value of all elements in the array to null
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

    /**
     * Appends all elements in the specified collection to the end of this list in the order returned by the Iterator of the specified collection.
     */
    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 elements from the specified collection into this list, starting at the specified location.
     */
    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;
    }

    /**
     * Remove all elements from this list that have an index between fromIndex (inclusive) and toIndex.
     *Move any subsequent elements to the left (reduce their index).
     */
    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;
    }

    /**
     * Check whether the given index is in range.
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

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

    /**
     * Return IndexOutOfBoundsException details
     */
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

    /**
     * Removes all elements contained in the specified collection from this list.
     */
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        //Returns true if this list is modified
        return batchRemove(c, false);
    }

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


    /**
     * Returns a list iterator for the elements in the list (in the correct order) starting at the specified position in the list.
     *The specified index indicates that the first element returned by the initial call is next. Initial call to previous returns the element with the specified index minus 1.
     *The list iterator returned is fail-fast.
     */
    public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }

    /**
     *Returns the list iterator in the list in the appropriate order.
     *The list iterator returned is fail-fast.
     */
    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

    /**
     *Returns the iterators of the elements in the list in the correct order.
     *The iterator returned is fail-fast.
     */
    public Iterator<E> iterator() {
        return new Itr();
    }


3. Analysis of ArrayList Expansion Mechanism

3.1. Start with the constructor of ArrayList

(JDK8) There are three ways to initialize an ArrayList, the construction method source code is as follows:

   /**
     * Default initial capacity size
     */
    private static final int DEFAULT_CAPACITY = 10;


    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     *Default constructor to construct an empty list using initial capacity 10 (parameterless construction)
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * Constructor with initial capacity parameter. (user specified capacity)
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {//Initial capacity greater than 0
            //Create an array of initialCapacity sizes
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {//Initial capacity equal to 0
            //Create an empty array
            this.elementData = EMPTY_ELEMENTDATA;
        } else {//Initial capacity less than 0, throwing exception
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }


   /**
    *Constructs a list containing the specified collection elements that are returned sequentially using the iterator of the collection
    *If the specified collection is null, throws NullPointerException.
    */
     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;
        }
    }

Careful students will surely find that when you create an ArrayList with a parameterless construction method, you are actually initializing an empty array of values. Capacity is truly allocated when adding elements to an array is actually done. That is, when the first element is added to the array, the array capacity expands to 10. Here's what we'll tell you when we analyze the ArrayList extension!

Supplement: When JDK6 new constructs an ArrayList object without parameters, it directly creates an Object[] array elementData of length 10.

3.2. Step by step analysis of the ArrayList expansion mechanism

An analysis of Array List created by parameterless constructor

3.2.1. Let's start with the add method

    /**
     * Appends the specified element to the end of this list.
     */
    public boolean add(E e) {
   //Call the ensureCapacityInternal method before adding elements
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //Here you can see that adding elements to an ArrayList is essentially equivalent to assigning values to an array
        elementData[size++] = e;
        return true;
    }

Note: JDK11 removes the ensureCapacityInternal() and ensureExplicitCapacity() methods

3.2.2. Take another look at the ensureCapacityInternal() method

(JDK7) You can see that the add method first calls ensureCapacityInternal(size + 1)

   //Obtain minimum capacity expansion
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              // Gets the default capacity and the larger value of the incoming parameter
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

When add ing into the first element, minCapacity is 1, and when compared with the Math.max() method, minCapacity is 10.

This is slightly different from subsequent JDK8 code formats, and the core code is basically the same.

3.2.3. ensureExplicitCapacity() method

If you call the ensureCapacityInternal() method, you will surely enter (execute) the method. Let's look at the source code of this method.

  //Determine if capacity expansion is required
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            //Call the group method for expansion, which means expansion has started
            grow(minCapacity);
    }

Let's take a closer look:

  • When we add the first element into the ArrayList, elementData.length is 0 (because it is still an empty list), because the ensureCapacityInternal() method is executed, minCapacity is 10 at this time. At this point, minCapacity - elementData.length > 0 is established, so it enters the grow(minCapacity) method.
  • When the second element adds, minCapacity is 2, where e lementData.length (capacity) expands to 10 after the first element is added. At this point, minCapacity - elementData.length > 0 is not established, so it will not enter the (execute) grow(minCapacity) method.
  • When adding elements 3, 4, and 10, the group method is still not executed, and the array capacity is 10.

Until the eleventh element is added, the minCapacity(11) is larger than the elementData.length (10). Enter the group method for expansion.

3.2.4. grow() method

    /**
     * Maximum array size to allocate
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * ArrayList Core approach to capacity expansion.
     */
    private void grow(int minCapacity) {
        // oldCapacity is old capacity, newCapacity is new capacity
        int oldCapacity = elementData.length;
        //Move oldCapacity one bit to the right, which is equivalent to oldCapacity/2.
        //We know that bit operations are much faster than integer division operations, and that the result of an entire sentence operation is to update the new capacity to 1.5 times the old capacity.
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //Then check if the new capacity is greater than the minimum required capacity or less than the minimum required capacity, and treat the minimum required capacity as the new capacity of the array.
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
       // If the new capacity is greater than MAX_ARRAY_SIZE, enter (execute) `hugeCapacity()` method to compare minCapacity with MAX_ARRAY_SIZE,
       //If minCapacity is greater than the maximum capacity, the new capacity is `Integer.MAX_VALUE`, otherwise the new capacity size is MAX_ARRAY_SIZE is `Integer.MAX_VALUE - 8`.
        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);
    }

Int newCapacity = oldCapacity + (oldCapacity > > 1), so the capacity of ArrayList will change to about 1.5 times after each expansion (even oldCapacity is 1.5 times, otherwise it is about 1.5 times)! Parity is different, for example: 10+10/2 = 15, 33+33/2 = 49. If it is odd, decimals will be discarded.

">" (Shift Operator): >> 1 To the right is to remove 2, and to the right is to divide 2 to the n-th power. Here oldCapacity is significantly moved one bit to the right, so it's equivalent to oldCapacity/2. For binary operations on large data, the displacement operator is much faster than those of ordinary operators, because the program just moves a bit without calculating, which improves efficiency and saves resources

Let's explore the growth () method again with examples:

  • When the first element adds, oldCapacity is 0, which is determined by the first if after comparison, newCapacity = minCapacity(10). However, the second if judgment is not valid, that is, newCapacity is no better than MAX_ ARRAY_ Large SIZE does not enter the hugeCapacity method. Array capacity is 10, return true in add method, increase size to 1.
  • When the add11th element enters the group method, newCapacity is 15, larger than minCapacity (11), and the first if judgment is not valid. The new capacity is not larger than the maximum size of the array and will not enter the hugeCapacity method. The array capacity is expanded to 15, the add method return true, and the size to 11.
  • And so on.

Here is a more important but often overlooked point of knowledge:

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

3.2.5. hugeCapacity() method.

From the growth() method source above, we know that if the new capacity is greater than MAX_ARRAY_SIZE, enter (execute) hugeCapacity() method to compare minCapacity with MAX_ARRAY_SIZE, if minCapacity is greater than maximum capacity, the new capacity is Integer.MAX_VALUE, otherwise the new capacity size is MAX_ARRAY_SIZE is Integer.MAX_VALUE - 8.

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //For minCapacity and MAX_ARRAY_SIZE comparison
        //If minCapacity is large, Integer.MAX_VALUE as the size of the new array
        //If MAX_ARRAY_SIZE large, MAX_ARRAY_SIZE as the size of the new array
        //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

3.3. System.arraycopy() and Array.copyOf() methods

If you read the source code, you'll see a lot of calls to these two methods in ArrayList. For example, this method is used in the extension operations we mentioned above, as well as add(int index, E element), toArray(), and so on!

3.3.1. System.arraycopy() method

Source code:

    // We found arraycopy to be a native method, so let's explain what each parameter means.
    /**
    *   Copy Array
    * @param src Source Array
    * @param srcPos Starting position in source array
    * @param dest target array
    * @param destPos Starting position in target array
    * @param length Number of array elements to copy
    */
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

Scene:

    /**
     * Insert the specified element at the specified location in this list.
     *First call rangeCheckForAdd to check the bounds of the index; Then call the ensureCapacityInternal method to ensure that capacity is large enough;
     *Move all the members back one place after the index starts; Insert element into index position; Finally size plus 1.
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //arraycopy() method implements that the array copy 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;
        size++;
    }

Let's write a simple way to test the following:

public class ArraycopyTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] a = new int[10];
		a[0] = 0;
		a[1] = 1;
		a[2] = 2;
		a[3] = 3;
		System.arraycopy(a, 2, a, 3, 3);
		a[2]=99;
		for (int i = 0; i < a.length; i++) {
			System.out.print(a[i] + " ");
		}
	}

}

Result:

0 1 99 2 3 0 0 0 0 0

3.3.2. Arrays.copyOf() method

Source code:

    public static int[] copyOf(int[] original, int newLength) {
    	// Request a new array
        int[] copy = new int[newLength];
	// Call System.arraycopy, copy the data from the source array, and return the new array
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

Scene:

   /**
     Returns an array (from the first to the last element) containing all the elements in this list in the correct order; The runtime type of the returned array is the runtime type of the specified array.
     */
    public Object[] toArray() {
    //elementData: The array to be copied; size: the length to copy
        return Arrays.copyOf(elementData, size);
    }

I feel that the Arrays.copyOf() method is mainly used to extend the original array, and the test code is as follows:

public class ArrayscopyOfTest {

	public static void main(String[] args) {
		int[] a = new int[3];
		a[0] = 0;
		a[1] = 1;
		a[2] = 2;
		int[] b = Arrays.copyOf(a, 10);
		System.out.println("b.length"+b.length);
	}
}

Result:

10

3.3.3. Connections and differences between the two

Contact:

Looking at the source code for both, you can see that the System.arraycopy() method is actually called inside copyOf()

Difference:

arraycopy() requires a target array to copy the original into an array or array you define. You can choose the starting point and length of the copy and the position copyOf() placed in the new array to automatically create a new array internally and return the array.

3.4. ensureCapacity method

There is a ensureCapacity method in the source code of ArrayList. I don't know if you noticed it. This method is not called internally in ArrayList, so it is obviously provided for users to call. So what does this method do?

    /**
    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) {
            ensureExplicitCapacity(minCapacity);
        }
    }

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

We actually test the effectiveness of this method with the following code:

public class EnsureCapacityTest {
	public static void main(String[] args) {
		ArrayList<Object> list = new ArrayList<Object>();
		final int N = 10000000;
		long startTime = System.currentTimeMillis();
		for (int i = 0; i < N; i++) {
			list.add(i);
		}
		long endTime = System.currentTimeMillis();
		System.out.println("Use ensureCapacity Before method:"+(endTime - startTime));

	}
}

Run result:

Use ensureCapacity Pre-method: 2158
public class EnsureCapacityTest {
    public static void main(String[] args) {
        ArrayList<Object> list = new ArrayList<Object>();
        final int N = 10000000;
        list = new ArrayList<Object>();
        long startTime1 = System.currentTimeMillis();
        list.ensureCapacity(N);
        for (int i = 0; i < N; i++) {
            list.add(i);
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println("Use ensureCapacity After method:"+(endTime1 - startTime1));
    }
}

Run result:

Use ensureCapacity After method: 1773

By running the results, we can see that it is best to use the ensureCapacity method before adding a large number of elements to the ArrayList to reduce the number of incremental reallocations.

Tags: Java Container source code array

Posted on Mon, 06 Dec 2021 12:20:50 -0500 by Nikos7