Java Collection Framework Learning - List: Expansion Mechanism of ArrayList

The Java collection framework is very important and has learning significance. Today , this article came into being by in-depth study of the expansion mechanism  of the ArrayList implementation class under its List interface .

Table of contents

start

ArraysSupport.newLength method

What is the constant value SOFT_MAX_ARRAY_LENGTH

Adding multiple elements at once exceeds the original capacity of the ArrayList

System.arraycopy method


start

The bottom layer of ArrayList is actually a simple array used to store Object type. If it is not set, the initialization length is 0

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

where DEFAULTCAPACITY_EMPTY_ELEMENTDATA is an empty array of length 0.

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

So when does ArrayList initialize its capacity? According to the source code documentation of ArrayList, when the first element is added, any empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA will be expanded to DEFAULT_CAPACITY .

 And what is this DEFAULT_CAPACITY   ? The following is a small demo, take us to understand together. 

public class test01 {
    public static void main(String[] args) {
        outOfIndexWithAddOne();

//
    }

    private static void outOfIndexWithAddOne() {
        ArrayList<String> list = new ArrayList<>();
        list.add("你好");
        String[] strs = {"a","a","a","a","a","a","a","a","a"};
        list.addAll(Arrays.asList(strs));
        // -----------------------------------
        list.add("你好啊"); //TODO expanding the previous capacity.
        System.out.println(list);
    }

}

When the add() method is called to add the first element to the ArrayList, the expansion mechanism of the array starts to work.

    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

//

    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

//

    private Object[] grow() {
        return grow(size + 1);
    }

The grow() method is the focus of the expansion mechanism

    private Object[] grow(int minCapacity) {
        /*
            添加第一个元素时,oldCapacity 的值是0,if条件不成立
            执行 new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
            此时 minCapacity 的值为 1
    
        */
        int oldCapacity = elementData.length;
        if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            int newCapacity = ArraysSupport.newLength(oldCapacity,
                    minCapacity - oldCapacity, /* minimum growth */
                    oldCapacity >> 1           /* preferred growth */);
            return elementData = Arrays.copyOf(elementData, newCapacity);
        } else {
            return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
        }
    }
    private static final int DEFAULT_CAPACITY = 10;

Therefore, when adding the first element to the ArrayList container , the container capacity is initialized to 10 .

If you then store elements in the container to exceed the initial capacity of 10, the container will execute the expansion mechanism, which is the branch of the if condition.

ArraysSupport.newLength method

Let's briefly introduce the newLength method provided by the tool class ArraysSupport

    public static int newLength(int oldLength, int minGrowth, int prefGrowth) {}

Such a public static method requires three parameter values, namely:

  • oldLength – current length of the array (must be nonnegative) : current container capacity, must be a non-negative value
  • minGrowth – minimum required growth amount (must be positive) : The minimum expansion amount, which must also be a positive number
  • prefGrowth – preferred growth amount: preferred value-added (capacity expansion)

Let's take a look at its specific workflow

        if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            int newCapacity = ArraysSupport.newLength(oldCapacity,
                    minCapacity - oldCapacity, /* minimum growth */
                    oldCapacity >> 1           /* preferred growth */);
            return elementData = Arrays.copyOf(elementData, newCapacity);
        }


        public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
            // preconditions not checked because of inlining
            // assert oldLength >= 0
            // assert minGrowth > 0

            int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
            if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
                return prefLength;
            } else {
                // put code cold in a separate method
                return hugeLength(oldLength, minGrowth);
            }
        }


The expanded capacity is based on the old capacity plus the appropriate expansion length. have to be aware of is

int prefLength = oldLength + Math.max(minGrowth, prefGrowth);

May cause overflow. At this time, you need to use the hugeLength() method

    private static int hugeLength(int oldLength, int minGrowth) {
        int minLength = oldLength + minGrowth;
        if (minLength < 0) { // overflow
            throw new OutOfMemoryError(
                "Required array length " + oldLength + " + " + minGrowth + " is too large");
        } else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
            return SOFT_MAX_ARRAY_LENGTH;
        } else {
            return minLength;
        }
    }

In this method, if the expansion does exceed the maximum value that the int integer can store,  an OutOfMemoryError exception will be thrown. Otherwise, compare it with the value of SOFT_MAX_ARRAY_LENGTH, and return the most suitable result for expansion.

What is the constant value SOFT_MAX_ARRAY_LENGTH

Here we talk about this SOFT_MAX_ARRAY_LENGTH

It is the soft maximum array length imposed by the array growth calculation . Some JVMs (such as HotSpot) have implementation limitations that will cause an OutOfMemoryError ("Requested array size exceeds VM limit") to be thrown if an array of a certain length is requested to be allocated near Integer.MAX_VALUE even if sufficient heap is available. Actual limits may depend on certain JVM implementation-specific characteristics, such as object header size. The soft maximum is chosen conservatively so as to be smaller than any implementation limits that may be encountered.

    public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;

The above is the initialization capacity and expansion mechanism of ArrayList. But if the capacity of the ArrayList cannot support the one-time addition of a large number of elements, how can the expansion mechanism be implemented? Let's study it

Adding multiple elements at once exceeds the original capacity of the ArrayList

When the addAll() method is called to fill elements into the array in batches at one time, the capacity of the array is not enough to support adding, so another expansion mechanism is needed instead of slowly expanding the capacity of the array on the original basis.

Watch the demo first

public class test01 {
    public static void main(String[] args) {
        outOfIndexWithAddAll();
//
    }

    private static void outOfIndexWithAddAll() {
        ArrayList<String> list = new ArrayList<>();
        list.add("你好");
        String[] strs = {"a","a","a","a","a","a","a","a","a"};
        list.addAll(Arrays.asList(strs));
        // -----------------------------------
        String[] strs2 = {"b","b","b","b","b","b","b","b","b"}; //TODO expanding the previous capacity.
        list.addAll(Arrays.asList(strs2));
        System.out.println(list);
    }
}

The expansion mechanism is executed in addAll()

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        modCount++;
        int numNew = a.length;
        if (numNew == 0)
            return false;
        Object[] elementData;
        final int s;
        if (numNew > (elementData = this.elementData).length - (s = size))
            elementData = grow(s + numNew);
        System.arraycopy(a, 0, elementData, s, numNew);
        size = s + numNew;
        return true;
    }

If the number of added elements exceeds the capacity of the original container, then call the grow() method to perform expansion. At this time, the expansion is based on the number of added elements:

  1.  If the original container has not been filled, only expand the required capacity;
  2.  If the original container is full, expand the number of elements added

as shown in the picture

Finally, copy the data to the expanded container.

System.arraycopy method

Let me add the System.arraycopy method here

This method is a method modified by native, and its purpose is to copy between arrays or collections.

    @IntrinsicCandidate
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

It takes 5 parameter values:

  • Object src: source array
  • int srcPos: The starting position in the source array
  • Object dest: destination array
  • int destPos: the starting position in the destination array
  • int length: the length of the array element to be copied

Note: The starting position here refers to the starting position of the copy. int length refers to the number of elements to be copied from the original array to the target array

test

Guess you like

Origin blog.csdn.net/Ccc67ol/article/details/130474254