Source code analysis-ArrayList dynamic expansion mechanism

Body:
ArrayList inherits the AbstractList class and implements the List interface, and the bottom layer of ArrayList is a dynamically expanding array. ArrayList implements the RandomAccess interface, this interface is a random access mark interface (no need to traverse, directly access the memory address of the array element through the subscript), in addition, it also implements the Serializable interface to support serialization (that is, the object is converted into a character sequence Form, these character sequences include object fields and methods, serialized objects can be written to databases, files, and can also be used for network transmission), and ArrayList also supports copying and implements the Cloneable interface


Graphically shows the implementation and inheritance relationship of ArrayList:

Insert picture description here
ArrayList source code:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    
    

The ArrayList class implements the RandomAccess interface, which is a random access tag interface

//没有任何的方法
public interface RandomAccess {
    
    
}

How to directly access the memory address through the subscript? In fact, the following is done.
This is why the ArrayList query is fast.

 E elementData(int index) {
    
    
        return (E) elementData[index];
 }
 public E get(int index) {
    
    
     	//这个方法是检查下标是否越界
        rangeCheck(index);
        return elementData(index);
 }
 private void rangeCheck(int index) {
    
    
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
 }

The ArrayLarrist class implements the Cloneable interface, indicating that this class supports copying. The internals of ArrayLarrist do rewrite the clone() method of Object.

public Object clone() {
    
    
        try {
    
    
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
    
    
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
}

ArrayList dynamic expansion mechanism
ArrayList provides three initialization methods

 public ArrayList() 
 public ArrayList(Collection<? extends E> c)
 public ArrayList(int initialCapacity)

No-parameter construction

     /**
     * Constructs an empty list with an initial capacity of ten.
     */
  public ArrayList() {
    
    
   		//elementData指向了一个空数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
   }

The comment above the source code says that an array with a capacity of 10 is initialized, but elementData points to an empty array.
No-argument construction still does not explain the problem. Continue reading

     //默认初始化容量为10
    private static final int DEFAULT_CAPACITY = 10;
    
    private static final Object[] EMPTY_ELEMENTDATA = {
    
    };
    
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
    
    };
    
    //transient关键字作用:该字段不被序列化
    transient Object[] elementData; 
    
    private int size;

Parametric construction method (1)

 public ArrayList(int initialCapacity) {
    
    
        if (initialCapacity > 0) {
    
    
            this.elementData = new Object[elementData ];
        } else if (initialCapacity == 0) {
    
    
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
    
    
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
}

The initialCapacity parameter represents an initial capacity that can be understood as a capacity given when the object is initially created.
If the initialCapacity is greater than 0, the initial capacity of elementData is new Object[elementData]
If the initialCapacity is equal to 0, elementData points to an empty array.
If this initialCapacity is less than 0, throw an exception

This code will throw the following exception

java.lang.IllegalArgumentException: Illegal Capacity: -10

 List<Integer> list = new ArrayList<Integer>(-10);
        try {
    
    
            for (int i = 0; i < 10; i++) {
    
    
                list.add(i);
            }
            for (Integer element : list) {
    
    
                System.out.println(element);
            }
        }catch (Exception e){
    
    
            e.printStackTrace();
        }

Parametric construction method (2)

public ArrayList(Collection<? extends E> c) {
    
    
        elementDatca = 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;
        }
}

This parameterized structure uses a collection as the element of ArrayList.
ElementDatca is the array converted by c. Arrays.copyOf(elementData, size, Object[].class) Copy the contents of the array to the field elementData in the ArrayList class
.

Through the understanding of the construction method, it is still not possible to know how to dynamically expand, so the point is here

Suppose now that the eleventh element is added, the subscript (index) is 10, that is, the size is 10, execute ensureCapacityInternal(size + 1)

add() method

 public boolean add(E e) {
    
    
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
 }

The ensureCapacityInternal() method is a method to ensure capacity. In the ensureCapacityInternal method, calculateCapacity(elementData, minCapacity) determines whether elementData is an empty array. If it is, use Math.max(DEFAULT_CAPACITY, minCapacity); compare it to a larger one, and return it at this time The
value of minCapacity minCapacity is 11, execute the method ensureExplicitCapacity, determine whether minCapacity exceeds the current collection capacity elementData.length, if it exceeds, execute grow(minCapacity), now minCapacity is 11, so continue to execute the grow method


   private void ensureCapacityInternal(int minCapacity) {
    
    
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
  private static int calculateCapacity(Object[] elementData, int minCapacity) {
    
    
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
  }
    private void ensureExplicitCapacity(int minCapacity) {
    
    
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

Execute the grow method to assign the length of the array (this length is the default 10) to the oldCapacity
execution statement int newCapacity = oldCapacity + (oldCapacity >> 1); here involves the right shift operator and the
right shift operator: the right side removes the part Abandon, the left complement sign bit (0 means integer 1 means complex number) means: 10>>1
0000 1010 —> 0000 0101 After shifting to the right, it is decimal 5,
so it is expanded to 1.5 times the original, continue to judge if the new capacity is not available If the current minCapacity is exceeded,
point the new capacity to minCapacity. If the new capacity exceeds the maximum capacity, execute hugeCapacity(minCapacity); if the
above judgments are not satisfied, execute Arrays.copyOf(elementData, newCapacity); copy the original array to the new allocation Memory address

//默认的容量 如果超过这个容量就扩容
private static final int DEFAULT_CAPACITY = 10;
private void grow(int minCapacity) {
    
    
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        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);
    }

Get the size of the capacity through reflection

  public static Integer getCapacity(ArrayList list) {
    
    
        Integer length = null;
        Class clazz = list.getClass();
        Field field;
        try {
    
    
            field = clazz.getDeclaredField("elementData");
            field.setAccessible(true);
            Object[] object = (Object[]) field.get(list);
            length = object.length;
            return length;
        } catch (Exception e) {
    
    
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return length;
    }

The process of verifying the dynamic expansion of the array:

  • Does not exceed the default capacity
public class ArrayListTest {
    
    
    public static void main(String[] args){
    
    

         ArrayList<Integer> list = new ArrayList<Integer>();
        for (int i = 1; i < 5; i++) {
    
    
            list.add(i);
        }
        Integer capacity = getCapacity(list);
        System.out.println("集合容量:"+capacity);
        int size = list.size();
        System.out.println("集合大小:"+size);
		//集合容量:10
        //集合大小:4
    }
}
  • Exceed the default capacity
        ArrayList<Integer> list = new ArrayList<Integer>();
        List<Integer> newList = new ArrayList<Integer>();
        for (int i = 1; i < 15; i++) {
    
    
            list.add(i);
        }
        Integer capacity = getCapacity(list);
        System.out.println("集合容量:"+capacity);
        int size = list.size();
        System.out.println("集合大小:"+size);
		//集合容量:15
        //集合大小:14
  • If you increase by 5 first, and then increase by 15, the new capacity of the collection is equal to the minimum capacity
 		ArrayList<Integer> list = new ArrayList<Integer>();
        ArrayList<Integer> newList = new ArrayList<Integer>();
        for (int i = 1; i <= 5; i++) {
    
    
            list.add(i);
        }
        for (int i = 0; i < 15; i++) {
    
    
            newList.add(i);
        }
        list.addAll(newList);
		//集合容量:20
		//集合大小:20
  • If you add 20 elements, according to the previous understanding, when the 11th element is added, the capacity is expanded to 15 through oldCapacity + (oldCapacity >> 1). When 16 elements are added, the capacity will continue to expand. At this time, the new capacity is 22. , So the collection capacity is 22 and the collection size is 20
        ArrayList<Integer> list = new ArrayList<Integer>();
        for (int i = 1; i <= 20; i++) {
            list.add(i);
        }
        //集合容量:22
       //集合大小:20

Guess you like

Origin blog.csdn.net/lirui1212/article/details/108897123