Knowledge Literacy--Collection--The Expansion Mechanism of ArrayList

1. Initialization method

The biggest difference between ArrayList and array is that ArrayList realizes dynamic expansion and the size of the array is fixed. The bottom layer of ArrayList is realized by array.

image-20210321102453535

Let’s take a look at some of the variables of ArrayList.

	//默认大小
    private static final int DEFAULT_CAPACITY = 10;

	//空数组对象
    private static final Object[] EMPTY_ELEMENTDATA = {
    
    };

	//默认无参构造器的空数组,与EMPTY_ELEMENTDATA区分开是为了知道何时扩容了多少当第一个元素添加的时候
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
    
    };

	//存放数据的缓存变量,在添加第一个元素的时候将被扩展为DEFAULT_CAPACITY
    transient Object[] elementData; // non-private to simplify nested class access

	//元素数量
    private int size;

ArrayList has three initialization methods


    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() {
    
    
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    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;
        }
    }
  • If the initial capacity is not passed in, the default capacity will be used and set elementDatatoDEFAULTCAPACITY_EMPTY_ELEMENTDATA
  • When the initial capacity is passed in, it will be judged whether the value of initialCapacity is greater than 0. If it is greater than 0, a new array will be created, and if it is equal to 0, it will be directly set to

EMPTY_ELEMENTDATA

  • Pass in a Collection, first call toArray to assign elementData to it, and also judge whether its length is 0, if it is 0, set it elementDatato EMPTY_ELEMENTDATA.

Two, dynamic expansion

The first is to come to the add method

public boolean add(E e) {
    
    
    //确保容量足够
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //添加元素
    elementData[size++] = e;
    return true;
}
private void ensureCapacityInternal(int minCapacity) {
    
    
    // 判断elementData是不是默认的空数组
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
        // 取得两个参数中的最大值:DEFAULT_CAPACITY --> 10
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    
    
    //记录变更的次数与线程的安全性有关
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

When the current required minCapacity capacity is greater than elementData.length, it needs to be expanded, that is to say, the core method of the entire expansion is on grow.

private void grow(int minCapacity) {
    
    
    // overflow-conscious code
    int oldCapacity = elementData.length;
    //新的容量大小为:之前容量的大小 + (之前容量的大小 / 2) 注:“>>”的意思为除以2的1次方
    int newCapacity = oldCapacity + (oldCapacity >> 1);
     // 扩容后的容量小于前容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 扩容后的容量大于arraylist最大容量时
    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);
}

First, figure out the meaning of the three key variables:

  • minCapacity: The minimum capacity required for this expansion
  • oldCapacity: the original array capacity before expansion
  • newCapacity: estimated capacity after expansion

The biggest doubt here is newCapacity-MAX_ARRAY_SIZE

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

The value of MAX_ARRAY_SIZE is Integer.MAX_VALUE-8. If you read my Synchronized lock implementation principle, it should be clear that the virtual machine will store an additional 32bit or 8Byte array size in the object header for the array object, then this is reduced by 8. Can understand

length content Description
32/64bit Mark Word Store the hashcode or lock information of the object
32/64bit Class Metadata Address Pointer to store object type data
32/32bit Array length The length of the array

Then go down to chase the hugeCapacity method

private static int hugeCapacity(int minCapacity) {
    
    
    //溢出了 则变为负数
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

I found that a judgment was made here: minCapacity> MAX_ARRAY_SIZE, which means that if minCapacity (the current required capacity) is greater than MAX_ARRAY_SIZE, then the array capacity is directly assigned to Integer.MAX_VALUE? So, I didn't mean that the maximum capacity of the array is Integer.MAX_VALUE-8. How could this be copied directly? Note that the source code interpretation of MAX_ARRAY_SIZE says that there are some virtual machines. If minCapacity is indeed greater than MAX_ARRAY_SIZE at this time, then the virtual machine will be ignored and directly assigned to Integer.MAX_VALUE

Three, fast failure mechanism

Collections in Java provide an error mechanism called fail-fast, which can only be used to detect errors. When multiple threads operate on the contents of the same collection, a fail-fast mechanism may occur

For example, calling the remove method during iteration will throw ConcurrentModificationException

public class FastFailTest {
    
    
    public static void main(String[] args) {
    
    
        // 构建ArrayList
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        for (int i : list) {
    
    
            System.out.println(i);
            list.remove(1);
        }
    }
}

The above method summarizes the use of the remove method in the iteration process, and will throw an exception. The reason is that the value of modCount will be checked during the iteration process, but calling the remove method will cause modCount++, which will throw an exception when judging that the two modCounts are different.

The correct approach should be to use the remove method of the iterator, because using the remove method of the iterator will modify the value of expectedModCount so that the fail-fast mechanism will not be triggered

In a high-concurrency environment, ArrayList is thread-unsafe and will trigger the ail-fast mechanism. For specific solutions, please refer to multi-threading from entry to advanced (4)-collection safety class in multi-threaded environment-List

Guess you like

Origin blog.csdn.net/weixin_44706647/article/details/115046132