Deep analysis of ArrayList source code

Keywords: Java less

concept

ArrayList is a dynamic array whose capacity can grow dynamically

Inheritance diagram:

Source code

Let's look at it from the source code point of view:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
//Default capacity size            
private static final int DEFAULT_CAPACITY = 10;

//Returns the ArrayList array when the capacity of the ArrayList is specified to be zero
private static final Object[] EMPTY_ELEMENTDATA = {};

//Returns the array when no ArrayList capacity is specified
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//Storing data
transient Object[] elementData; 

//Number of elements in ArrayList
private int size;

summary

  • The default capacity of ArrayList is 10
  • The bottom layer of ArrayList is actually an array, using elementData arrays to store data.

Note: you can see that elementData is identified by transient, which means elementData can not be serialized. Why do you want to set it up?
Because not all elements in elementData have data, because of the capacity problem, some elements in elementData are empty, which is not necessarily serialized.
The serialization and deserialization of ArrayList depends on the writeObject and readObject methods. You can avoid serializing empty elements.

constructor

//Construct an empty array with an initial capacity of 10
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//Construct an empty array 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);
    }
}

//Construct an array of specified elements
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray may not return Object [] (as the comment says), so here's a look at the type
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        //If the length of the incoming c is 0, replace it with an empty array
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

The constructor is very simple, let's not talk about it here!

Additive elements

/*
 * Add elements to collections
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);//Judging whether ArrayList needs to be expanded
    elementData[size++] = e;
    return true;
}

/*Judging whether expansion is needed --> minCapacity is the minimum capacity required by a set*/
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

/*Returns the size of the added element*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //When adding an element for the first time, return minCapacity > 10? MinCapacity: 10 (it is possible to add a large number of elements using the addAll method for the first time)
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

/*Judging whether expansion is needed*/
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;//modCount is a variable inherited from AbstractList that represents the number of times a collection has been modified
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

/*Capacity expansion*/
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);//Expansion to 1.5 times the original capacity
    if (newCapacity - minCapacity < 0)//If the expanded capacity is still less than the minimum capacity, set minCapacity to the capacity size
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
        newCapacity = hugeCapacity(minCapacity);
    //Call Arrays.copyOf to generate a new array
    elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0)
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

So far, we can know the basic implementation of add (E):
When adding elements, first check if the array is large enough, and if not, expand to 1.5 times the original size.
After expansion, if the capacity is still less than minCapacity, the capacity will be expanded to minCapacity.

So how do you add elements at the specified location? Simple. Look at the source code directly.

/*
 * Adding elements at specified locations
 */
public void add(int index, E element) {
    rangeCheckForAdd(index);//Parameter checking

    ensureCapacityInternal(size + 1);
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
    elementData[index] = element;
    size++;
}
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

Posted by Tonisius on Sun, 06 Oct 2019 15:39:48 -0700