3. ArrayList source code analysis

1. Data structure The data structure of
Insert picture description here
ArrayList is an array. As shown in the figure above, the figure shows an array with a length of 10, counting from 1, index indicating the subscript of the array, counting from 0, elementData indicating array elements . In addition, there are three basic concepts in the source code.
Source code

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    
    //该值用于定义数组第一次扩容时的大小
    private static final int DEFAULT_CAPACITY = 10;

    transient Object[] elementData;

    //该值用于定义当前数组的大小
    private int size;
}

Source code analysis

  1. DEFAULT_CAPACITY represents the size of the array when it is first expanded. This value is often mentioned during interviews and it is also mentioned during initialization.
  2. size represents the size of the current array, the type is int, and volatile is not modified, which is not thread-safe.
  3. modCount represents the number of revisions of the current array. If the array structure changes, it will be +1.

2. Initializing
ArrayList provides three initialization methods, direct initialization without parameters, initialization with specified size, and initialization with specified initial data. The source code is as follows.
Source code

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
    
    };

    //无参数直接初始化,数组大小为空
    public ArrayList() {
    
    
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
   
    //指定大小初始化
    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);
        }
    }

    //指定数据初始化
    public ArrayList(Collection<? extends E> c) {
    
    
        //该值用于保存数组的容器,默认为null
        elementData = c.toArray();
        //如果给定的集合c有值
        if ((size = elementData.length) != 0) {
    
    
            //如果集合元素类型不是Object类型,则转成Object类型
            if (elementData.getClass() != Object[].class) {
    
    
                elementData = Arrays.copyOf(elementData, size, Object[].class);
            }
        } else {
    
    
            //给定的集合c无值
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
}

Source code analysis

  1. When the ArrayList parameterless constructor is initialized, the default size is an empty array, which is not the usual 10, which is the array value expanded when the element is added for the first time.
  2. When specifying data initialization, you can see that the comment contains such a comment "c.toArray might (incorrectly) not return Object[] (see 6260652)". This is a bug in the source code, meaning that when an element in a given collection is not of type Object, it will be converted to type Object. Under normal circumstances, this bug will not be triggered. It will only be triggered in the following scenarios: After the ArrayList is initialized (the ArrayList element is not of Object type), the toArray method is called again to get the Object array, and then it will be triggered when the Object array is assigned. The bug, the specific case code and the screenshot of the running result are as follows.
public class App {
    
    
    public static void main(String[] args) {
    
    
        List<String> list = Arrays.asList("hello world");
        Object[] objArray = list.toArray();
        //打印的值为String[]
        System.out.println(objArray.getClass().getSimpleName());
         //抛出ArrayStoreException异常
        objArray[0] = new Object();
    }
}

Insert picture description here

3. Adding and expanding
Adding is to add elements to the array, which is mainly divided into two steps. One is to determine whether expansion is needed, and if necessary, to perform expansion operations, and the other is to directly assign values. The specific source code is as follows.
Source code

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    
    public boolean add(E e) {
    
    
        //确保数组大小是否足够,不够执行扩容,size为当前数组的大小
        ensureCapacityInternal(size + 1);
        //直接赋值,这里是线程不安全的
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
    
    
        //如果初始化数组大小时,有给定初始值,以给定的大小为准,不走if逻辑
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //确保容积足够
        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
    
    
        //记录数组被修改
        modCount++;
        //如果期望的最小容量大于目前数组的长度,即扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    //扩容,并把现有数据拷贝到新的数组里
    private void grow(int minCapacity) {
    
    
        int oldCapacity = elementData.length;
        //oldCapacity >> 1是把oldCapacity除以2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果扩容后的值小于期望值,扩容后的值就等于期望值
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //如果扩容后的值大于jvm所能分配的数组的最大值,那么就用Integer的最大值
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //通过复制进行扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
}

Source code analysis After the
expansion is completed, add elements directly to the array through elementData [size++] = e. It is through this simple assignment that there is no lock control, so the operation here is thread-unsafe.

to sum up

  1. The rule for expansion is not to double, it is the original capacity plus half the capacity. To put it bluntly, the expanded capacity is 1.5 times the original capacity.
  2. The maximum value of the array in ArrayList is Integer.MAX_VALUE. If this value is exceeded, the JVM will not allocate memory space for the array.
  3. When it was added, the value was not strictly checked, so ArrayList allows null values.

4. Iterator
To implement an iterator, you only need to implement the java.util.Iterator class. The iterator has three important parameters and methods. The source code is as follows.
Source code

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    
    //迭代过程中,下一个元素的位置,默认从0开始
    int cursor;
    /**
     * 新增场景表示上一次迭代过程中,索引的位置
     * 删除场景为-1
     */
    int lastRet = -1;

    /**
     * expectedModCount表示迭代过程中,期望的版本号
     * modCount表示数组实际的版本号
     */
    int expectedModCount = modCount;

    public boolean hasNext() {
    
    
        //cursor表示下一个元素的位置,size表示实际大小,如果两者相等,说明没有可以迭代的元素,如果不等,说明还有元素需要迭代
        return cursor != size;
    }

    public E next() {
    
    
        //迭代过程中,判断版本号是否被修改,若被修改,抛出ConcurrentModificationException异常
        checkForComodification();
        //本次迭代过程中,元素的索引位置
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        //下一次迭代时,元素的位置,为下一次迭代做准备
        cursor = i + 1;
        //返回元素值
        return (E) elementData[lastRet = i];
    }

    //版本号比较
    final void checkForComodification() {
    
    
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

    public void remove() {
    
    
        //如果上一次操作时,数组的位置已经小于0,说明数组已经被删除完
        if (lastRet < 0)
            throw new IllegalStateException();
        //迭代过程中,判断版本号是否被修改,若被修改,抛出ConcurrentModificationException异常
        checkForComodification();

        try {
    
    
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            //-1表示元素已经被删除,这里也有防止重复删除的作用
            lastRet = -1;
            //删除元素时modCount的值已经发生变化,在此赋值给expectedModCount,下次迭代时,两者的值便是一致的
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
    
    
            throw new ConcurrentModificationException();
        }
    }
}

Source code analysis

  1. As you can see from the source code, the next method accomplishes two things. The first is to check whether the iteration can continue, and the second is to find the value of the iteration and prepare for the next iteration (cursor + 1).
  2. The purpose of lastRet = -1 is to prevent duplicate deletion.
  3. If the element is successfully deleted, the current modCount of the array will change, and the expectedModCount will be reassigned here, and the values ​​of the two will be the same in the next iteration.

Guess you like

Origin blog.csdn.net/Jgx1214/article/details/109064315