ArrayList - Understanding common methods based on source code

Keywords: Programming less Java

ArrayList should be one of the most common list implementation classes in your usual code. It should be a surprise to learn more about common methods by looking at the source code, as well as infrequent ones.This time, look at the corresponding source in terms of method familiarity, it is necessary to paste the source.

1. ArrayList header comments

This class was introduced beginning with java 1.2.It is an adjustable array implementation of the List interface.Implements all optional list operations and allows all elements, including null.In addition to implementing the List interface, this class provides methods to manipulate the size of arrays internally used to store lists.This class is roughly equivalent to Vector, but it is not synchronous.

The size, isEmpty, get, set, iterator, and listIterator operations run over a constant period of time.The add operation runs in amortization fixed time, which means that adding n elements requires O(n) time.All other operations run in linear time (generally).The constants implemented by LinkedList are lower than those implemented by LinkedList.

Each ArrayList instance has a Capacity.Capacity is the size of the array used to store elements in the list.It is always at least as large as the list size.When an element is added to an ArrayList, its capacity automatically increases.Details of the growth strategy are not specified, except that the add element has a fixed amortization time cost.Applications can use ensureCapacity to increase the capacity of ArrayList instances before adding a large number of elements to an operation, which can reduce the number of incremental reassignments.

Keep in mind that this implementation is not synchronized.If multiple threads access the ArrayList instance at the same time and at least one thread has structurally modified the list, it must be synchronized externally.(Structural modification is any operation that adds or deletes one or more elements or explicitly resizes the underlying array; setting only the value of an element is not a structural modification.)This is usually achieved by synchronizing some objects of the naturally encapsulated list.If no such object exists, you should use Collections.synchronizedList Method Packaging List.This is best done at creation time to prevent accidental asynchronous access to lists:

List list = Collections.synchronizedList(new ArrayList(...));

The iterators returned by such iterator iterators and listIterator methods are quick failures: if the list is structurally modified at any time after the iterator is created, the iterator will throw {@link ConcurrentModificationException} except through the iterator's own ListIterator remove () or ListIterator add (Object).Therefore, in the face of concurrent modifications, iterators fail quickly and cleanly without risking arbitrary and indeterminate behavior in the future indeterminate times.Note that the iterator's fast failing behavior cannot be guaranteed, as it is generally impossible to make any strict guarantees in the presence of concurrent, asynchronous modifications.Iterators that fail quickly do their best to throw ConcurrentModificationException.Therefore, it is incorrect to write programs that rely on this exception to ensure program correctness: the iterator's fast failure behavior should only be used to detect errors.

2. Common methods

2.1 Construction Method

The most common is the empty parameter construction method, so take a look at the source implementation:

/**
 * Construct an empty list with an initial capacity of 10
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
 * SharedEmpty arrayInstances forDefault sizeEmpty instance.
 * We'll compare this with EMPTY_ELEMENTDATA distinguishes,To see how much to expand when adding the first element. 
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
 * Array buffer that stores ArrayList elements.The capacity of the ArrayList is the length of this array buffer.
 * When adding the first element, any with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * All empty ArrayList s willExtend to DEFAULT_CAPACITY.
 */
transient Object[] elementData; // non-private to simplify nested class access Non-private to simplify access to nested classes

Construction Method Note: Construct an empty list with an initial capacity of 10, but DEFAULTCAPACITY_EMPTY_Neither ELEMENTDATA nor elementData is satisfied, and the elementData variable comment has a description that when the first element is added, any with elementData == DEFAULTCAPACITY_EMPTY_The empty ArrayList of ELEMENTDATA will be expanded to DEFAULT_CAPACITY.Viewing the add method ultimately calls the following methods:

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//Note that the internal array object is the default empty array instance
        return Math.max(DEFAULT_CAPACITY, minCapacity);//The larger one,minCapacity The first addition is for 1
    }
    return minCapacity;
}
/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)  //hereElementData.length= 0
        grow(minCapacity);
}

As you can see from the above, an instance of an array list created by a parameterless construction method with length = 0 of the underlying array expands the capacity of the array to 10 when the first element is added (logic is not advanced at all), where the capacity of the list is 10.

You can specify how to construct the initialization capacity:

/**
 * Constructs an empty list with the specified initial capacity.
 */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
/**
 * Shared empty array instance for empty instances.
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

The initialization capacity is greater than 0, create an array object of a specified size and assign it to the underlying array variable directly; when the initialization capacity is equal to 0, assign a value to the underlying array variable using the shared empty array instance, note that the capacity will not be expanded to 10 when the first element is added, and only if the internal array is the default empty array instance will initialize the capacity to 10.

Contains the construction method for the specified set

/**
 * Constructs a list containing elements of a specified set whose order is returned by the iterator of the set.
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();//Collection to Array, different collection interface subclasses are implemented differently
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652) There is an array type that is not Object[] Situation
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.Empty Array Use Shared Empty Array Instance
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

The code knows that if the size of the incoming collection object is equal to 0, the array variable inside the list is assigned to the shared empty array instance; otherwise, the array variable inside the list is assigned to the converted array object and the type is guaranteed to be Object[]

The relationship between order and iterator is not clear yet.

2.2 Add Action

Commonly used are: adding a specified element at the end of a column, adding a specified element at a specified location, adding all elements of a specified collection, and adding all elements of a specified collection at a specified location, which is temporarily unused.

Add a specified element to the end of the column

/**
 * Appends the specified element to the end of this list.
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
private void ensureCapacityInternal(int minCapacity) {//Ensure adequate internal capacity
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {//Calculate capacity, mainly by setting the list initialization capacity created by the empty parameter construction method to 10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {//Determine an exact capacity to expand when the minimum capacity is larger than the internal array size
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
/**
 * Increase capacity to ensure that it accommodates at least the number of elements specified by the minimum capacity parameter.
 * Increment is half its original size
 *  If the added new capacity is less than the expected minimum capacity, the new capacity value should be the minimum capacity
 *  If the added new capacity is greater than the constant maximum array size, call the large capacity method to return the value as the new capacity value
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    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);
}
private static int hugeCapacity(int minCapacity) {If the expected minimum capacity is greater than the constant maximum array size, the new capacity is the maximum integer value; otherwise, the new capacity is the constant maximum array size
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}
/**
 * Maximum array size to allocate.Some virtual machines retain 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;

Adding a specified element at a specified location

/**
 * Inserts the specified element into the specified location in this list.
 * Move the current element (if any) at that location and any subsequent elements to the right (add its index to 1).
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
    rangeCheckForAdd(index);//First check if the subscript is out of bounds and throw the agreed exception

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);//Moves the specified position and the following elements one place to the right
    elementData[index] = element;//Places the specified element in the specified location
    size++;
}
/**
 * add And addAllRanCheck version used.
 */
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

Adding all elements of a specified collection

/**
 * Appends all elements in the specified collection to the end of this list in the order returned by the Iterator of the specified collection.
 * If the specified set is modified during the operation, the behavior of the operation is uncertain. 
 *(This means that if the specified collection is this list and the list is not empty, the behavior of the call is uncertain.)
 * @return <tt>true</tt> if this list changed as a result of the call
 * @throws NullPointerException if the specified collection is null
 */
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); //Exceptions are thrown in this method
    size += numNew;
    return numNew != 0;//List changes after invocation returns true
}

Adding a specified set at a specified location

/**
 * Inserts all elements from the specified collection into this list starting at the specified location.
 * Move elements (if any) that are currently at that location and any subsequent elements to the right (increase their index).
 * The new elements will appear in the list in the order returned by the iterator for the specified set.
 * @return <tt>true</tt> if this list changed as a result of the call
 * @throws IndexOutOfBoundsException {@inheritDoc}
 * @throws NullPointerException if the specified collection is null
 */
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;
}

In the addAll method, the default is the Object [] type when the specified set is converted to an array, which is somewhat different from the Object [] type when the specified set is converted to an array in the construction method. It is not clear for the moment that the existence type of the converted array is not Object [].

2.3 Query Operations

Here, first look at querying elements based on a specified subscript, and then look at the two methods of querying subscripts based on a specified element separately after an iterator query.

Query by Subscript

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

    return elementData(index);
}
/**
 * Check whether the given index is in range.If not, throw the appropriate runtime exception
 * This method does not check if the index is negative: always use it immediately before the array is accessed, if the index is negative,
 * Then throw ArrayIndexOutOfBoundsException. 
 */
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

Query by element

/**
 * Returns the specified element in this listFirst-time IndexReturns -1 if the list does not contain the element.
 * More formally, returns the lowest index <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
 * or -1 if there is no such index.
 */
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;
} //When looking for subscripts,Traverse from small to large starting at 0,Returns the first occurrence of a subscript value; pairs null Value Usage == Determine whether the specified object is used by other objects equals Method to determine if it is the same object.
/**
 * Returns the specified element in this listLast index;Returns -1 if the list does not contain the element.
 * More formally, returns the highest index <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
 * or -1 if there is no such index.
 */
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;
}When looking for subscripts,from size-1 Start traversal from large to small,Returns the subscript value that first appears;
/**
 * Returns true if the list contains the specified element.
 * More formally, returns <tt>true</tt> if and only if this list contains
 * at least one element <tt>e</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
 */
public boolean contains(Object o) {
    return indexOf(o) >= 0; //Call the indexOf method to determine if the specified element is included based on whether the return value is greater than or equal to 0, and if not, indexOf returns -1 less than 0 to return false
}

2.4 Modification Action

/**
 * Replace the element at the specified location in this list with the specified element.Return replaced elements
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

2.5 Delete operation

Delete the specified location element

/**
 * Delete the element at the specified location in this list.Move all subsequent elements to the left (with index minus 1).
 * Return deleted elements
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

Delete the specified element

/**
 * Removes the first occurrence of the specified element from the list and returns true if it exists.
 * If the list does not contain this element, it remains unchanged and returns false.
 */
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,Skip boundary checking and do not return deleted values.
 */
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
}

Private delete method fastRemove(int index) is called there by this method, which differs from deleting a specified location element by not requiring boundary checking (traversal calls) or returning deleted values. Other logic is consistent.

Empty list

/**
 * Remove all elements from this list.When the call returns, the list will be empty.
 */
public void clear() {
    modCount++;

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

    size = 0;
}//The internal array is null at every position, but the size of the array is unchanged; the list size is set to zero

Delete elements within a specified range

/**
 * Remove index from this list in fromIndex(Included) and toIndex (excluded)All elements between.
 * Move all subsequent elements to the left (reduce their index).This call shortens the list through the (toIndex-fromIndex) element. 
 * (If toIndex == fromIndex}, this operation is invalid.)
 *
 * @throws IndexOutOfBoundsException if {@code fromIndex} or
 *         {@code toIndex} is out of range
 *         ({@code fromIndex < 0 ||
 *          fromIndex >= size() ||
 *          toIndex > size() ||
 *          toIndex < fromIndex})
 */
protected void removeRange(int fromIndex, int toIndex) {
    modCount++;
    int numMoved = size - toIndex;
    System.arraycopy(elementData, toIndex, elementData, fromIndex,
                     numMoved);//Exception thrown in this method

    // clear to let GC do its work
    int newSize = size - (toIndex-fromIndex);
    for (int i = newSize; i < size; i++) {
        elementData[i] = null;
    }
    size = newSize;
}

Delete contained specified collection elements

/**
 * Removes all elements contained in the specified collection from this list.
 * Returns true because this method was called to change the list
 * @throws ClassCastException if the class of an element of this list
 *         is incompatible with the specified collection
 * (<a href="Collection.html#optional-restrictions">optional</a>)
 * @throws NullPointerException if this list contains a null element and the
 *         specified collection does not permit null elements Question 1
 * (<a href="Collection.html#optional-restrictions">optional</a>),
 *         or if the specified collection is null
 * @see Collection#contains(Object)
 */
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c); //Check if the specified collection is empty
    return batchRemove(c, false);
}
public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)//foreach
            if (c.contains(elementData[r]) == complement) // Answer Question 1 here
                elementData[w++] = elementData[r];// Stores elements not contained in the specified collection (complement = false)
    } finally {
        // Preserve behavioral compatibility with AbstractCollection, Question 2, what kind of behavior?
        // even if c.contains() throws. 
        if (r != size) {//c.contains() will not meet the criteria until it jumps out of the loop abnormally
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {//Satisfies a condition indicating that an element is contained in a specified set, and the list changes to be deleted
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}

Posted by Think Pink on Wed, 27 May 2020 10:23:59 -0700