[This article is to sort out the summary articles of knowledge and summarize some important points of knowledge that I think are relevant, in order to consolidate memory and technical exchanges and forget criticism and correction. It refers to a lot of previous articles, including pictures are also quoted, if offensive, invasive deletion. ]
0. Storage structure
From the bottom implementation point of view, Array is an array implementation, and unlike an array, its capacity can be changed. When the collection expands, it creates a larger array space and copies the original data into the new array. ArrayList supports fast random access to elements, but insertion and deletion are usually slow because the process is likely to require moving other elements.
The 1 Definition
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
2. Static constants
// Default initial capacity size private static final int DEFAULT_CAPACITY = 10; // Empty arrays (for empty instances) private static final Object[] EMPTY_ELEMENTDATA = {}; // Shared empty array instances for default size empty instances. // We distinguish it from the EMPTY_ELEMENTDATA array to see how much additional capacity is required to add the first element. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // The theoretical maximum that an array can allocate private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
3 attribute
/** * An array of ArrayList data */ transient Object[] elementData; // non-private to simplify nested class access /** * ArrayList Number of elements contained */ private int size;
4. Constructor
Mainly used to initialize arrays.
/** * Constructor with initial capacity parameter. */ public ArrayList(int initialCapacity) { // Initial Capacity > 0, then directly create the corresponding length of the array if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { // Create empty arrays using EMPTY_ELEMENTDATA representation this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * Default constructor, denoted by DEFAULTCAPACITY_EMPTY_ELEMENTDATA as an empty array. * When the first element is added, an array with a length of 10 is actually allocated. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * In the order in which they are returned by the iterator of the set, a list of elements containing the specified set is constructed. */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); // If the number of specified collection elements is not zero if ((size = elementData.length) != 0) { // Determine whether c.toArray returns an array of Object type, if not, replicate with Arrays.copyOf if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // Replace empty arrays this.elementData = EMPTY_ELEMENTDATA; } }
5 Common methods
add method
- Ensure that the array can drop the element and expand if the capacity is insufficient.
- Appends the specified element to the end of the list
public boolean add(E e) { // To judge whether the expansion is needed, the group method is used to expand the capacity if the expansion is needed. ensureCapacityInternal(size + 1); // Increments modCount!! // Appends the specified element to the end of the list elementData[size++] = e; return true; }
To determine whether expansion is needed, call the grow() method if expansion is needed
// Get the Minimum Expansion Capacity private void ensureCapacityInternal(int minCapacity) { // ElementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA indicates that the current array length is the default length of 10 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // See if the length of a new node is larger than the default length minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } //Judging whether expansion is needed private void ensureExplicitCapacity(int minCapacity) { // New additions are structural modifications modCount++; // If it is larger than the current array length, it needs to be expanded if (minCapacity - elementData.length > 0) grow(minCapacity); }
Capacity expansion method
- To determine the size of the new capacity, first expand to 1.5 times, if not enough, directly use the required capacity;
- If the required capacity exceeds the theoretically allocated maximum, the maximum capacity is used.
- Copy the array to a new capacity array.
// ArrayList Expansion Method private void grow(int minCapacity) { // Old Capacity is the old capacity, and new Capacity is the new capacity. int oldCapacity = elementData.length; // The new capacity is 1.5 times the rescue capacity. int newCapacity = oldCapacity + (oldCapacity >> 1); // If the new capacity still does not meet the requirements, use minCapacity directly as the new capacity if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // If the new capacity is greater than the maximum capacity defined by ArrayList, set it to MAX_ARRAY_SIZE if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // Copy to a new array using replicated Arrays.copyOf elementData = Arrays.copyOf(elementData, newCapacity); }
Array replication method
Arrays.copyOf() method is sometimes used in the source code, and System.arraycopy() method is sometimes used for array replication. In fact, the bottom of the Arrays.copyOf() method is to call the System.arraycopy() method, while the System.arraycopy() method is a Native method.
Arrays.copyOf (T [] original, int new Length) method
Copy the specified original array to make it newLength in length.
Valid values in the original array will be included in the new array, and the extra part of the new array will be null.
The underlying implementation calls System.arraycopy(original, 0, copy, 0, Math. min (original. length, new Length);
System.arraycopy(Object src, int srcPos, Object dest, int destPos,int length) method
The original array srcPos is copied from the starting position of the original array srcPos to the destPos position of the destination array dest, and the length of the replication is length.
parameter | Explain |
src | Original array |
srcPos | Initial position of primitive array |
dest | target array |
destPos | Starting position of target array |
length | Number of array elements to replicate |
// Arrays.copy method source code public static <T> T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); } // The underlying layer uses the System.arraycopy() method to copy old array data to new arrays public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { // Declare a new array of length newLength @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); // System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } // Is a native method public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
get method
- Simply check if index is larger than array length.
- Returns the element directly.
/** * Returns the element at the specified location in the ArrayList. */ public E get(int index) { // Array boundary checking for index rangeCheck(index); // Elements that return directly to that location return elementData(index); } // Check only if it is larger than the array length private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
remove method
- Index boundary checking
- Self-Increasing Modifications
- Save the elements on index to oldValue
- Move all the elements on index one bit forward
- Empty the last element to allow the garbage collector to recycle
- Return the original value oldValue
// Delete the element at the specified location of the ArrayList public E remove(int index) { // Boundary check rangeCheck(index); modCount++; E oldValue = elementData(index); // Calculate the number of elements that need to be moved int numMoved = size - index - 1; // Move the element behind index one bit forward if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // null the last position to help GC elementData[--size] = null; // clear to let GC do its work return oldValue; }
remove specifies object methods
- There are two kinds of processing logic according to whether the object is empty or not.
- If the removal object is empty, the first null object is searched in turn, and then removed.
- If the removal object is not empty, it is searched sequentially and the same elements are found for removal.
// Remove the specified object public boolean remove(Object o) { // There are two kinds of processing logic based on whether the object is empty or not. // Remove empty objects and find the first null object in turn if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { // If the object is not empty, the relative elements are searched in turn for removal. for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } // Skipping checks directly removes elements at specified locations, the same logic as remove(int index) 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 }
Best practices
- Estimate the required capacity of the scenario and set it to the initial value to prevent multiple scaling and affect performance (especially when the data volume is large).
Reference
- https://zhuanlan.zhihu.com/p/34443888
- Code Out Efficient Java Development Manual