brief introduction
ArrayList is a List implemented as an array, which has the ability to dynamically expand compared to arrays, so it can also be called a dynamic array.
Inheritance System
- ArrayList implements List, RandomAccess, Cloneable, java.io.Serializable and other interfaces.
- ArrayList implements List and provides basic add, delete, traverse operations.
- ArrayList implements RandomAccess, providing the ability to access randomly.
- ArrayList implements Cloneable and can be cloned.
- ArrayList implements Serializable and can be serialized.
Source Code Analysis
/** * The default capacity, which is 10, is the default capacity when created through the new ArrayList(). */ private static final int DEFAULT_CAPACITY = 10; /** * An empty array, if used when the incoming capacity is 0, is created with the new ArrayList(0). */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Empty array, used when passing in capacity, restarted to default capacity size when adding the first element * This is created with the new ArrayList() using this empty array. * The difference from EMPTY_ELEMENTDATA is that when the first element is added, using this empty array initializes to DEFAULT_CAPACITY (10) elements. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * Array storing elements * Where elements are actually stored, transient s are used to not serialize this field. */ transient Object[] elementData; // non-private to simplify nested class access /** * Number of elements in the collection * The number of elements actually stored, not the length of the elementData array. */ private int size;
ArrayList(int initialCapacity) construction method
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { // If the initial capacity passed in is greater than 0, create a new array storage element this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { // If the initial incoming capacity equals 0, use the empty array EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } else { // Throw an exception if the initial capacity passed in is less than 0 throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); } }
ArrayList() construction method
public ArrayList() { // If no initial capacity is passed in, use the empty array DEFAULTCAPACITY_EMPTY_ELEMENTDATA // Using this array expands to the default size of 10 when the first element is added this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
ArrayList construction method
/** * Initialize the elements passed into the collection into the ArrayList */ public ArrayList(Collection<? extends E> c) { // Set to Array elementData = c.toArray(); if ((size = elementData.length) != 0) { // Check that c.toArray() returns an Object [] type, and if not, re-copy it to Object[].class type if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // If c is an empty collection, it is initialized to an empty array EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } }
c.toArray() here; since the return may not be of type Object[], see the following code:
class MyList extends ArrayList<String> { /** * Subclass overrides parent class's method and returns can be different * But you can only use array types here, not Object * Should be a bug in java itself */ @Override public String[] toArray() { // Write to death directly for convenience return new String[]{"1", "2", "3"}; } }
Add(E) method
Add elements to the end with an average time complexity of O(1).
public boolean add(E e) { // Check if expansion is required ensureCapacityInternal(size + 1); // Insert the element last elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { // If it is an empty array DEFAULTCAPACITY_EMPTY_ELEMENTDATA, it is initialized to the default size of 10 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) // Expansion grow(minCapacity); } private void grow(int minCapacity) { int oldCapacity = elementData.length; // The new capacity is 1.5 times the old capacity int newCapacity = oldCapacity + (oldCapacity >> 1); // If the new capacity is found to be smaller than the required capacity, the required capacity will prevail if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // If the new capacity exceeds the maximum capacity, use the maximum capacity if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // Copy out a new array with new capacity elementData = Arrays.copyOf(elementData, newCapacity); }
- Check if expansion is required;
- If elementData equals DEFAULTCAPACITY_EMPTY_ELEMENTDATA, the initialization capacity is DEFAULT_CAPACITY;
- The new capacity is 1.5 times the old capacity (oldCapacity + (oldCapacity >> 1). If you add so much capacity and find that it is smaller than what you need, the capacity you need is the one you need.
- Create a new capacity array and copy the old array to the new array;
add(int index, E element) method
Add elements to the specified location with an average time complexity of O(n).
public void add(int index, E element) { // Check if it is out of bounds rangeCheckForAdd(index); // Check if expansion is required ensureCapacityInternal(size + 1); // Move inex and the elements that follow it one place back, and the index position is empty System.arraycopy(elementData, index, elementData, index + 1, size - index); // Insert element into index position elementData[index] = element; // Size increase 1 size++; } private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
- Check if the index is out of bounds;
- Check if expansion is required;
- Move the elements one place back after inserting them into the index position;
- Place the inserted element at the insertion index position;
- Size plus 1;
addAll method
Find the union of two sets.
/** * Add all elements from collection c to the current ArrayList */ public boolean addAll(Collection<? extends E> c) { // Convert set c to array Object[] a = c.toArray(); int numNew = a.length; // Check if expansion is required ensureCapacityInternal(size + numNew); // Copy all elements in c to the end of the array System.arraycopy(a, 0, elementData, size, numNew); // Size increases the size of c size += numNew; // Returns true if c is not empty, false otherwise return numNew != 0; }
get(int index) method
Gets the element at the specified index location with a time complexity of O(1).
public E get(int index) { // Check if it is out of bounds rangeCheck(index); // Returns the element at the index position of the array return elementData(index); } private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } E elementData(int index) { return (E) elementData[index]; }
- Check if the index is out of bounds, here only check if it is out of bounds, if an IndexOutOfBoundsException exception is thrown over the upper bound, and if an ArrayIndexOutOfBoundsException exception is thrown over the lower bound.
- Returns the element at the index position;
remove(int index) method
Deletes the element at the specified index location with a time complexity of O(n).
public E remove(int index) { // Check if it is out of bounds rangeCheck(index); modCount++; // Get the element at index position E oldValue = elementData(index); // If index is not last, move the element after index forward one position int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // Delete the last element to help the GC elementData[--size] = null; // clear to let GC do its work // Return Old Value return oldValue; }
- Check if the index is out of bounds;
- Gets the element at the specified index position;
- If the last element is not deleted, the other elements move forward by one place.
- null the last position for GC recycling;
- Returns the deleted element.
As you can see, ArrayList deletes elements without indentation.
remove(Object o) method
Deletes an element of a specified element value with a time complexity of O(n).
public boolean remove(Object o) { if (o == null) { // Traverse the entire array, find where the element first appears, and delete it quickly for (int index = 0; index < size; index++) // If the element to be deleted is null, compare it with null, using == if (elementData[index] == null) { fastRemove(index); return true; } } else { // Traverse the entire array, find where the element first appears, and delete it quickly for (int index = 0; index < size; index++) // If the element to be deleted is not null, compare using the equals() method if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } private void fastRemove(int index) { // There is a missing cross-border check modCount++; // If index is not last, move the element after index forward one position int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // Delete the last element to help the GC elementData[--size] = null; // clear to let GC do its work }
- Find the first element equal to the specified element value;
- Quick deletion;
fastRemove(int index) has fewer operations to check index boundaries than remove(int index), so jdk optimizes performance to the extreme.
retainAll method
Find the intersection of two sets.
public boolean retainAll(Collection<?> c) { // Collection c cannot be null Objects.requireNonNull(c); // Call the bulk delete method, and complement passes in true, indicating deletion of elements not contained in c return batchRemove(c, true); } /** * Bulk deletion of elements * complement Delete elements not contained in c for true * complement Delete elements contained in c for false */ private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; // Traversing the array simultaneously using two pointers, read and write // Each time the read pointer adds 1, the write pointer adds 1 when it places an element // This does not require any additional space, it just needs to operate on the original array int r = 0, w = 0; boolean modified = false; try { // Traverse the entire array, and if c contains the element, place it at the write pointer (complement prevails) for (; r < size; r++) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // Normally r ends up equal to size unless c.contains() throws an exception if (r != size) { // If c.contains() throws an exception, copy all unread elements after the write pointer System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // Leave the element after the write pointer empty to help the GC for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; // The new size is equal to the location of the write pointer (because every write pointer is added 1, the new size is exactly equal to the location of the write pointer) size = w; modified = true; } } // Return true with modifications return modified; }
- Traverse the elementData array;
- If the element is in c, add it to the w position of the elementData array and move the w position one bit backward;
- After traversal, elements before w are common to both, and elements after w are not common to both;
- null elements after w for GC recycling;
removeAll
Finds the one-way difference between two sets, keeping only those elements that are not in c in the current set and not those that are not in the current set in c.
public boolean removeAll(Collection<?> c) { // Collection c cannot be empty Objects.requireNonNull(c); // Also call the bulk delete method, when complement passes in false, meaning delete the element contained in c return batchRemove(c, false); }
summary
- ArrayList uses arrays to store elements internally. When the length of the arrays is insufficient, the ArrayList will not be scaled by adding half the space each time.
- ArrayList supports random access, accessing elements through an index is extremely fast, and the time complexity is O(1);
- ArrayList adds elements to the tail very quickly, with an average time complexity of O(1);
- ArrayList adds elements to the middle slowly because the average time complexity to move elements is O(n);
- ArrayList deletes elements from the tail very quickly with an O(1) time complexity;
- ArrayList deletes elements from the middle more slowly because the average time complexity to move elements is O(n);
- ArrayList supports union, call addAll (Collection<? Extends E> c) method;
- ArrayList supports intersection, call the retainAll (Collection<? Extends E> c) method;
- ArrayList supports one-way difference set, call removeAll (Collection<? Extends E> c) method;