Source code analysis of ArrayList based on jdk1.8
When we talk about ArrayList, we may immediately think of it as follows: orderly, repeatable, fast search, but slow add and delete, and unsafe thread. But the specific reasons are not very clear, this article will study with you according to these problems. It mainly analyzes the construction method of ArrayList, adding elements, deleting elements, getting elements, querying elements, clearing elements, judging whether elements exist and traversing ArrayList.
1: The implementation of ArrayList
1.1: constructor
ArrayList<> list = new ArrayList(); ArrayList<> list2 = new ArrayList(10);
1.2: member variable
//Default initialized capacity private static final int DEFAULT_CAPACITY = 10; //Shared empty array instance for empty instance. private static final Object[] EMPTY_ELEMENTDATA = {}; //Shared empty array instance for the default size empty instance. The difference is that this one has a default capacity private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //Array buffer to store elements of ArrayList. The capacity of ArrayList is the length of the array buffer. //When any empty ArrayList with elementdata = = defaultaccess? Empty? Elementdata adds the first element, //Expand to default u capacity. transient Object[] elementData; //The size of the ArrayList (the number of elements it contains). private int size;
Resolution: when the object is serialized (write byte sequence to the target file), transient prevents the variables declared with this keyword from being persisted in the instance; when the object is deserialized (read byte sequence from the source file for reconstruction), such instance variable values will not be persisted and recovered.
1.3: concrete implementation of constructor
//Method 1: public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } //Method two: 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); } }
Parsing: the two methods are basically the same. The difference is that one has default capacity and the other is an empty array. From here, we can see that the bottom layer of ArrayList is realized by array, because the real storage element here is realized by elementData array.
//Method three: (not commonly used) 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; } }
Parsing: we seldom use the third method, and we do a little parsing here. Here, we mainly get an object array through the Collection.toArray() method, then assign it to elementData, and initialize it by judging the length of the array. First, assign the length of the array to size, and then make a judgment. If the length of the array is not empty, then use Arrays.copyOf() to copy the elements in collection c to the elementData array. If it is empty, then the empty array of empty? elementData will be assigned to elementData. Let's take a look at the Arrays.copyOf() method.
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @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; }
Parsing: This is mainly through the transfer of the array, and size to complete the process of copying from the old array to the new array. First, we will judge whether it is a new object or a reflection based on the Class type to create an object and place it in a new array. Next is the process of array copy. The source code of System.arraycopy() is as follows:
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
Parsing: this method will be used all the time. After understanding, we will study the following content. The specific implementation here is to copy the src array from the element with the subscript of srcPos, copy the length elements into the dest array, and add the elements into the dest array from the subscript of destPos. Note that both src and dest must be arrays of the same type or can be converted.
1.4: add elements
There are four kinds of added elements: we analyze them one by one.
arrayList.add(Object element); boolean arrayList.add(int index, Object element); void arrayList.addAll(Collection<? extends E> c); boolean arrayList.addAll(int index,Collection<? extends E> c); boolean
1.4.1: arrayList.add(Object element) source resolution:
//Represents the minimum value of int public static final int MIN_VALUE = 0x80000000; //Represents the maximum value of int public static final int MAX_VALUE = 0x7fffffff; //The maximum array size to allocate. //As an object, array needs a certain amount of memory to store the object header information. The maximum memory occupied by the object header information cannot exceed 8 bytes. private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; public boolean add(E e) { //Assign the initial length to judge whether to expand. The current length is size+1 ensureCapacityInternal(size + 1); // Increments modCount!! //New elements elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { //If the array length is 0, a large value will be selected between 10 and size+1 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { //Record the number of changes (0 by default). Inconsistent iterations will trigger the fail fast mechanism. Therefore, you should use Iterator.remove() to delete elements during traversal modCount++; // overflow-conscious code //If the initial length is greater than the length of the element, you need to expand the capacity if (minCapacity - elementData.length > 0) //Capacity expansion grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code //Old capacity int oldCapacity = elementData.length; //New capacity = 1.5 times the old capacity int newCapacity = oldCapacity + (oldCapacity >> 1); //If the new capacity is less than the capacity specified at the beginning, assign the starting capacity to the new capacity if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //If the new capacity is greater than the maximum length of the array, specify the new capacity if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //Array conversion assignment, as mentioned above elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { //If the initial specified capacity is less than 0, an error is reported directly if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //If the initial specified capacity of a ternary expression is greater than the maximum length of the array, then the maximum value of int is selected, otherwise the maximum length of the array is selected return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
1.4.2: arrayList.add(int index, Object element) source resolution:
public void add(int index, E element) { //Determine whether the array subscript is out of range rangeCheckForAdd(index); //Assign the initial length to determine whether to expand the capacity. The current length is size+1 (it has been analyzed in detail above) ensureCapacityInternal(size + 1); // Increments modCount!! //Move the size index elements of the original array from the index position to the next one to make room for the new elements System.arraycopy(elementData, index, elementData, index + 1, size - index); //Assign a value to the vacated position elementData[index] = element; size++; } private void rangeCheckForAdd(int index) { //If the subscript is larger than the existing size of the array or the subscript is smaller than 0, an out of range subscript exception will be thrown if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
1.4.3: arraylist.addall (collection <? Extensions E > C) source resolution:
public boolean addAll(Collection<? extends E> c) { //Convert to array Object[] a = c.toArray(); int numNew = a.length; //Assign the initial length to determine whether to expand the capacity. The current length is size+ numNew (which has been analyzed in detail above) ensureCapacityInternal(size + numNew); // Increments modCount //Add all elements in the collection to the end of this list System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }
1.4.4: arraylist.addall (int index, collection <? Extends E > C) source resolution:
public boolean addAll(int index, Collection<? extends E> c) { //Determine whether the subscript is out of range rangeCheckForAdd(index); //Convert to array Object[] a = c.toArray(); int numNew = a.length; // //Assign the initial length to determine whether to expand the capacity. The current length is size+ numNew (which has been analyzed in detail above) ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; //When the size of the array is larger than the index, move the last numMoved elements of the source array from the index position to the numNew bit //Then the new array will occupy the position of index+numNew if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }
To sum up: when adding data to arrayList, it will judge whether the subscript will cross the boundary when it comes to index. Under normal circumstances, it will judge whether to expand the capacity. If the capacity is expanded, it will expand 1.5 times each time. However, when the size of the newly expanded array has reached the maximum, it will take the maximum value.
1.5: delete element
There are three common methods to delete elements. We analyze them one by one:
arrayList.remove(Object o); boolean arrayList.remove(int index); String arrayList.removeAll(Collection<?> c); boolean
1.5.1: source resolution of arrayList.remove(Object o):
public boolean remove(Object o) { //null can be stored in arrayList, so the following judgment is made //Deleted element is null if (o == null) { //Find the first element to delete, //Because arrayList can store the same elements, the first one is deleted for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { //Deleted element is not null for (int index = 0; index < size; index++) //Find the element to delete if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } private void fastRemove(int index) { modCount++; //Judge the rationality of deleted elements int numMoved = size - index - 1; if (numMoved > 0) //The assignment operation of an array moves all elements after the index position in the elementData of the array forward one bit System.arraycopy(elementData, index+1, elementData, index, numMoved); //Give GC to process the empty data at the end elementData[--size] = null; // clear to let GC do its work }
1.5.2: source code resolution of arrayList.remove(int index):
public E remove(int index) { //Check whether the subscript is out of range rangeCheck(index); modCount++; //Records return deleted elements E oldValue = elementData(index); //Check the rationality of deleting elements int numMoved = size - index - 1; if (numMoved > 0) //Move all the elements after the index of the original array forward one bit System.arraycopy(elementData, index+1, elementData, index, numMoved); //Give GC to process the empty data at the end elementData[--size] = null; // clear to let GC do its work return oldValue; }
1.5.3: source resolution of arraylist.removeall (collection <? > C):
//Delete all elements in arrayList that contain c public boolean removeAll(Collection<?> c) { //Check if c is empty Objects.requireNonNull(c); return batchRemove(c, false); } public static <T> T requireNonNull(T obj) { //If it is null, it will directly throw null pointer if (obj == null) throw new NullPointerException(); return obj; } private boolean batchRemove(Collection<?> c, boolean complement) { //Used to store filtered elements final Object[] elementData = this.elementData; //Double pointer, r for traversal, w for recording the number of filtered elements int r = 0, w = 0; //It is used to record whether there is a change this time. If there is a change, it will return true boolean modified = false; try { for (; r < size; r++) //1: The parameter of composition passed in is false //2: If c contains the values in the array, then only r is increasing automatically. If c does not contain the values in the array, then both r and w are increasing automatically //3: It is a process of forward assignment. if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. //In general, r equals to size. When an exception occurs, r! = size will be caused //Then copy and overwrite all the data behind the exception to the array. if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } //When w is not equal to size, data is deleted if (w != size) { // clear to let GC do its work for (int i = w; i < size; i++) // w records the number of filtered elements, so later data can be set to null and submitted to GC for processing elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; }
1.6: modifying elements
arrayList.set(int index, E element)
1.6.1: source code resolution of arraylist.set (int index, e element):
public E set(int index, E element) { //Check whether the subscript is out of range rangeCheck(index); //Store old elements E oldValue = elementData(index); //Overwrite old value under specified subscript elementData[index] = element; //Return old value return oldValue; }
1.7: get element
arrayList.get(int index);
1.7.1: source code resolution of arrayList.get(int index):
public E get(int index) { //Determine whether the subscript is out of range rangeCheck(index); return elementData(index); } @SuppressWarnings("unchecked") E elementData(int index) { //Returns the element of the specified subscript return (E) elementData[index]; }
1.8: empty element
arrayList.clear();
1.8.1: arrayList.clear() source resolution:
public void clear() { modCount++; // clear to let GC do its work //Set all elements to null, and then submit to GC for processing for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
1.9: include specified elements
arrayList.contains(Object o);
1.9.1: arrayList.contains(Object o) source resolution:
public boolean contains(Object o) { return indexOf(o) >= 0; } public int indexOf(Object o) { //For null if (o == null) { //Return after traversing to find a null subscript for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { //Not null, return after finding the specific subscript for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } //Return - 1 without specifying an element return -1; }
1.10: iterator() method of arrayList
arrayList.iterator();
1.10.1: source code resolution of arrayList.iterator() iterator:
public Iterator<E> iterator() { return new Itr(); } private class Itr implements Iterator<E> { //Itr implements the Iterator interface, and obtains the hasNext and next methods of the Iterator interface //Record the subscript of the next element int cursor; // index of next element to return //Record the subscript of the last element int lastRet = -1; // index of last element returned; -1 if no such //Record the number of changes //When entering the iterator method of ArrayList, expectedModCount is initialized, but when the user is in the process //When add and remove are used, the value of modCount will be changed. The next time the value is not equal, a concurrent modification exception will be thrown int expectedModCount = modCount; Itr() {} public boolean hasNext() { //If the subscript of the next element is not equal to size, it means there are still elements return cursor != size; } @SuppressWarnings("unchecked") public E next() { //Check for concurrent modification exceptions checkForComodification(); int i = cursor; //When i is greater than size or the length of array, it means that there is concurrent operation, which will throw concurrent modification exception if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; //Return the next element when there is no exception return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() { //When the number of changes is inconsistent with the expected number of changes (the number of changes when calling the iterator method), concurrent modification exceptions will occur if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
2: Analysis of problems related to ArrayList
2.1: why is ArrayList fast to find, slow to add and delete?
Parsing: from the source code, we can see that when we find the corresponding subscript elements directly, we can get the specific elements and return them. However, adding elements may lead to the capacity expansion mechanism and involve the assignment operation of two sets, so it will slow down.
2.2: why is the thread unsafe in ArrayList?
Resolution: thread safety means that multiple threads access the same code (objects, variables, etc.) without uncertain results. But we can see through the source code interpretation that the ArrayList.add() method will produce uncertain results when it is used.
public boolean add(E e) { //Assign the initial length to judge whether to expand. The current length is size+1 ensureCapacityInternal(size + 1); // Increments modCount!! //New elements elementData[size++] = e; return true; }
From the above, elementData[size++] = e may cause thread insecurity. This step can be divided into two steps
elementData[size] = e; size = size + 1;
When multithreading begins to add elements, thread 1 begins to add element A At this time, thread 1 puts element A at the position with subscript 0, and thread 2 also adds element B when it comes in. When it comes in, it finds that there is no data at the position with subscript 0, and thread 2 puts the data at the position with subscript 0. Thread 1 and thread 2 continue their operation. Next, thread 1 will increase the size to 1, and when thread 2 comes in again, it will increase the size to 2, But at this time, the actual situation is that the size is 2, but the position with subscript 0 becomes B, and the position with subscript 1 is null, and the next adding element will start from the position with subscript 2. To sum up: ArrayList is thread unsafe.
2.3: how to avoid the thread insecurity of ArrayList.
Analysis:
The first method: using the Vertor set
Vector<Object> arrayListSafe1 = new Vector<Object>();
The second method: use Collections.synchronizedList
List<String> list = new ArrayList<String>(); //Can be changed into List<String> data=Collections.synchronizedList(new ArrayList<String>());
At this point, the source code research of ArrayList is over. Welcome to discuss and learn together.
For more dry cargo, welcome to my WeChat official account: Javanese sword.