ArrayList constructor
/** * Initial capacity */ private static final int DEFAULT_CAPACITY = 10; private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Object[] elementData; // non-private to simplify nested class access private int size; /** * Constructor with 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); } } /** * Default constructor, using the initial capacity to construct an empty list */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * Constructs a list of specified Collection elements that are returned sequentially using the Collection's iterators * NullPointerException is thrown if the specified collection is null */ 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; } }
When an ArrayList is created with the no argument constructor method, it is actually an empty array that initializes the assignment. When you add elements to an array, you actually allocate capacity. That is, when the first element is added to the array, the capacity of the array is expanded to 10.
Analyze the expansion mechanism of ArrayList
Take the parameterless constructor as an example
- add() method
/** * Add a development element to the end of this list */ public boolean add(E e) { //Call the ensurcapacityinternal method before adding elements ensureCapacityInternal(size + 1); // Increments modCount!! //Here we see that the essence of adding elements to ArrayList is to assign values to arrays elementData[size++] = e; return true; }
- Ensurcapacityinternal() method
//The add method first calls the ensurcapacityinternal (size + 1) private void ensureCapacityInternal(int minCapacity) { //Get the minimum expansion capacity if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }
When you want to add the first element, minCapacity=1. After comparing the Math.max() method, minCapacity=10
- Insureexplicitcapacity() method
//If you call ensureExplicitCapacity internal, the method ensureExplicitCapacity will be executed private void ensureExplicitCapacity(int minCapacity) { modCount++; //Judge whether expansion is needed // overflow-conscious code if (minCapacity - elementData.length > 0) //Call grow method to expand capacity grow(minCapacity); }
Analysis:
- When the first element is add ed to ArrayList, elementData.length is 0 (an empty list), because the ensurcapacityinternal() method is executed, minCapacity =10. At this time, mincapacity - elementdata. Length > 0 is established, so enter the grow(minCapacity) method.
- When the second element is added to ArrayList, minCapacity=2, the elementdata.length is expanded to 10 after the first element is added. At this time, mincapacity - elementdata. Length > 0 does not hold, so it will not enter the grow(minCapacity) method.
- Add 3rd, 4th In the 10th element, the growth (mincapacity) method is still not entered, and the number of arrays is 10
Until the 11th element is added, minCapacity(11) is larger than elementData.length(10). Enter the grow method for capacity expansion.
- grow() method
/** * Maximum array size to allocate */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * ArrayList The core method of capacity expansion */ private void grow(int minCapacity) { //Old capacity size, new capacity is new capacity int oldCapacity = elementData.length; //Move the old capacity one bit to the right, equivalent to oldCapacity/2 //The speed of bit operation is much faster than integer division operation. The result of whole sentence operation is to update the new capacity to 1.5 times the old capacity int newCapacity = oldCapacity + (oldCapacity >> 1); //Then check whether the new capacity is larger than the minimum required capacity. If it is still smaller than the minimum required capacity, then the minimum required capacity is regarded as the new capacity of the array if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //If the new capacity is greater than max array size, execute the 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 with 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); therefore, the capacity of ArrayList will become about 1.5 times of the original capacity after each expansion (oldCapacity is 1.5 times even, otherwise it will be about 1.5 times)! The parity is different, for example: 10 + 10 / 2 = 15, 33 + 33 / 2 = 49. If it is an odd number, the decimal point will be lost
"> >" (bit operator): > > 1 moving one bit to the right is equal to dividing by 2, and moving n bits to the right is equal to dividing by 2 to the nth power. Here, an obvious shift of 1 bit to the right of oldCapacity is equivalent to that of oldCapacity/2. For binary operations of big data, bit operators are much faster than those of ordinary operators, because the program only moves once, and does not calculate, which improves efficiency and saves resources
Look at the grow method
- When the first element is added, oldCapacity=0. After comparison, the first if is set, newCapacity = minCapacity(10). But the second if does not work, and the hugeCapacity() method will not be called. If the array capacity is 10, the add method returns true, size+1.
- When the 11th element of add calls the grow method, newCapacity =15, which is larger than minCapacity =11. The first if does not hold. The new capacity is not larger than the maximum size of the array, and the hugeCapacity method will not be called. Expand the array capacity by 15, and the add method returns true, size=11.
- And so on
Easily confused
- The length property is for arrays
- The length() method is for string length
- size() is for the collection of generics
- hugeCapacity method
If the new capacity is greater than max array size, the hugecapacity() method will be called to compare minCapacity and Max array size. If minCapacity is greater than max array size, the new capacity is integer > max value. Otherwise, the new capacity is Max array size, which is integer.max-value-8
private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //Compare 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; }
System.arraycopy() and Arrays.copyOf() methods
The two methods are invoked in ArrayList, and Arrays.copyOf() is called in grow().
System.arraycopy() is called in add(int index, E element).
- System.arraycopy() method
/** * Inserts the specified element at the specified location in the list * First call rangeCheckForAdd to check the bounds of index; then call the ensureCapacityInternal method to ensure that capacity is large enough. * Then move all the members from the beginning of index backward one position; insert element into index position; finally size+1 * * */ public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! //The arraycopy() method implements array copying itself //elementData: source array; index: start position in source array; elementData: target array; index+1: position in target array; size index: number of elements to be copied; System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
- Arrays.copyOf() method
//Returns an array (from the first to the last element) containing all elements in the 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); }
- Arrays.copyOf() and System.arraycopy() relationships
Contact: System.arraycopy() is actually called in copyOf()
Difference: System.arraycopy() needs to copy the original array to its own defined array or original array, and you can choose the starting point and length of the copy as well as the location in the new array; copyOf() is the system automatically creates a new array internally and returns the array.
Ensurcapacity method
This method has not been called in the ArrayList. It is obviously called for the user. What's the use???
/** * Increase the capacity of the ArrayList to ensure that it can hold 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 call the ensureCapacity() method before add bulk elements to reduce the number of incremental redistribution.
The comparison between the time spent using the ensurcapacity() method and the time spent not using it is as follows:
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: before using the ensurcapacity method: 2158
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 Methods:"+(endTime1 - startTime1)); }
Run result: after using the ensurcapacity method: 1773
Conclusion: ArrayList is best to use the ensurcapacity () method before adding a large number of elements to reduce the number of incremental reallocation.