ArrayList Source Details

Keywords: Java data structure linked list

1.Introduction to ArrayList

At the bottom of the ArrayList is an array queue, which is equivalent to a dynamic array. Its capacity can grow dynamically compared to arrays in Java. Applications can use the ensure Capacity operation to increase the capacity of ArrayList instances before adding a large number of elements. This can reduce 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 that indicates that the List collection that implements this interface supports fast random access. In ArrayList, we can quickly get an element object by its sequence 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.What is 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; LinkedList uses bi-directional chained list data structures (before JDK1.6, circular chained lists were cancelled by JDK1.7). Note the difference between bi-directional and bi-directional chained lists, described below!)
  3. Whether insertion and deletion are affected by element location: 1) ArrayList uses array storage, so the time complexity of inserting and deleting elements is affected by element location. For example, when the add (E) method is executed, ArrayList defaults to appending the specified elements to the end of this list, in which case the time complexity is O(1)However, if you want to insert and delete elements at the specified location I (add (int index, E element)), the time complexity will be O(n-i). Because the elements that follow the I and I elements i n the collection perform a backward/forward move by one position when doing this, LinkedList uses chain table storage, so for add (E)Method insertion, delete element time complexity is not affected by element location, approximate O(1), if you want to insert and delete element at the specified location I ((add(int index, E element)) time complexity is approximately o(n)) because you need to move to the specified location before inserting.
  4. Supports Quick Random Access: LinkedList does not support efficient random element access, whereas ArrayList does. Quick random access is the quick acquisition of 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 distinguish it from the EMPTY_ELEMENTDATA array to see how much capacity needs to be added 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;
        }
    }

    /**
     * Modifying the capacity of 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
     Set the maximum capacity to 0 if the minimum capacity set is not empty or set the maximum to 10, which is the initialization value
     */
    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, expansion is required
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
   //Obtain minimum capacity expansion
    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 it exceeds, hugeCapacity() is called to compare minCapacity with MAX_ARRAY_SIZE.
        //If minCapacity is greater than MAX_ARRAY_SIZE, 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;
    }

    /**
     * Return a shallow copy of this ArrayList instance. (Elements themselves are not copied.)
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            //The Arrays.copyOf function is to copy arrays and return the copied arrays. The parameters are the copied array 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 the 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. If the list fits the specified array, it is returned.
     *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 ensureCapacityInternal method to ensure that capacity is large enough;
     *Then move all the members back one place after the start of the index; insert the element into the index position; and finally add 1 to the size.
     */
    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 present) of the specified element from the list. If the list does not contain the 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. The 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 for 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 specifies 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 actually initialize the assignment to an empty array. When you really add elements to the array, you really allocate capacity. That is, when you add the first element to the array, the array capacity expands to 10. This is what we will 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.Let's look again 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), and minCapacityInternal () is executed, so minCapacity10. At this point, minCapacity - elementData.length > 0 is established, so it enters the grow(minCapacity) method.
  • When the second element adds, minCapacity is 2, then E lementData.length (capacity) expands to 10 after adding the first element. At this point, minCapacity - e lementData.length > 0 is not established, so it does not enter the (perform) 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, minCapacity(11) is larger than elementData.length (10). Enter the group method to expand.

3.2.4.Growth() 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. Decimals will be lost if it is odd.

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

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

  • When the first element adds, oldCapacity is 0. After comparison, the first if judgement is valid, newCapacity = minCapacity(10). However, the second if judgement is not valid, that is, newCapacity is not larger than MAX_ARRAY_SIZE and will not enter the hugeCapacity method. Array capacity is 10, return true and size increase to 1 in the add method.
  • 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 greater 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 is increased 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 compares 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();
        //Comparing minCapacity with MAX_ARRAY_SIZE
        //If minCapacity is large, use Integer.MAX_VALUE as the size of the new array
        //If MAX_ARRAY_SIZE is large, use 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 the ArrayList. For example, the extension operations we talked about above, as well as add(int index, E element), toArray(), and so on, all use this method!

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 ensureCapacityInternal method to ensure that capacity is large enough;
     *Then move all the members back one place after the start of the index; insert the element into the index position; and finally add 1 to the size.
     */
    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: the 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: Array to copy; size: 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.The Connection and Difference 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.

Posted by cyber_ghost on Wed, 08 Sep 2021 09:04:13 -0700